From afd340d2f1b19a3a8cddde78bb2a7d551de93a30 Mon Sep 17 00:00:00 2001 From: "@Robvred" <12579254+Robvred@users.noreply.github.com> Date: Thu, 23 Mar 2023 09:49:33 +0100 Subject: [PATCH 001/251] Add Unsupported Usage Types --- packages/aws/src/lib/CostAndUsageTypes.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/packages/aws/src/lib/CostAndUsageTypes.ts b/packages/aws/src/lib/CostAndUsageTypes.ts index 71fd1278c..0aa32dea3 100644 --- a/packages/aws/src/lib/CostAndUsageTypes.ts +++ b/packages/aws/src/lib/CostAndUsageTypes.ts @@ -46,6 +46,7 @@ export const SSD_USAGE_TYPES: string[] = [ 'ra3.16xlarge', // Redshift SSD 'Storage.SSD.50', // Fsx 'Storage.MultiAZ:SSD', // Fsx + 'RDS:GP3-Storage', // RDS ] export const HDD_USAGE_TYPES: string[] = [ @@ -98,6 +99,10 @@ export const HDD_USAGE_TYPES: string[] = [ 'WarmStorage-ByteHrs-DynamoDB', //AWSBackup 'MagneticStore-ByteHrs', // EBS Backup 'ColdStorage-ByteHrs-DynamoDB', //AWSBackup + 'WarmStorage-ByteHrs-S3', // S3 + 'AMP:MetricStorageByteHrs', + 'TimedStorage-INT-AIA-ByteHrs', // S3 Glacier + 'TimedStorage-GIR-ByteHrs', // S3 Glacier ] export const NETWORKING_USAGE_TYPES: string[] = [ @@ -169,6 +174,12 @@ export const UNKNOWN_USAGE_TYPES: string[] = [ 'Kafka.mcu.general', 'SnapshotArchiveStorage', 'PaidPrivateCA', + 'Firehose-VpcDelivery-Hours', + 'Airflow-MediumEnvironment', + 'IPAddressManager-IP-Hours', + 'Gateway:VTL-Storage', + 'Aurora:ServerlessV2Usage', // RDS Aurora + ] export const UNSUPPORTED_USAGE_TYPES: string[] = [ From 3f2bc9872898e4685e8138dd01b7a9331626a796 Mon Sep 17 00:00:00 2001 From: Arik Smith Date: Mon, 27 Mar 2023 11:57:27 -0400 Subject: [PATCH 002/251] Update packages/aws/src/lib/CostAndUsageTypes.ts removes extra whitespace --- packages/aws/src/lib/CostAndUsageTypes.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/aws/src/lib/CostAndUsageTypes.ts b/packages/aws/src/lib/CostAndUsageTypes.ts index 0aa32dea3..8e623cce1 100644 --- a/packages/aws/src/lib/CostAndUsageTypes.ts +++ b/packages/aws/src/lib/CostAndUsageTypes.ts @@ -179,7 +179,6 @@ export const UNKNOWN_USAGE_TYPES: string[] = [ 'IPAddressManager-IP-Hours', 'Gateway:VTL-Storage', 'Aurora:ServerlessV2Usage', // RDS Aurora - ] export const UNSUPPORTED_USAGE_TYPES: string[] = [ From 7e691413c003ffdffc5391372f63fc5e0d9fc142 Mon Sep 17 00:00:00 2001 From: Arik Smith Date: Mon, 27 Mar 2023 14:08:54 -0400 Subject: [PATCH 003/251] microsite: add documentation for azure rate limits and date chunking --- .../PerformanceConsiderations.md | 49 +++++++++++++++++-- 1 file changed, 45 insertions(+), 4 deletions(-) diff --git a/microsite/docs/ConfigurationOptions/PerformanceConsiderations.md b/microsite/docs/ConfigurationOptions/PerformanceConsiderations.md index 97215c513..ef38d551d 100644 --- a/microsite/docs/ConfigurationOptions/PerformanceConsiderations.md +++ b/microsite/docs/ConfigurationOptions/PerformanceConsiderations.md @@ -5,23 +5,64 @@ slug: /performance-considerations sidebar_position: 2 --- -### Options to Improve Query Performance +## Options to Improve Query Performance When running very large amounts of data with the default configuration of querying each day for the previous year, we have noticed that the time it takes to start the app increases significantly. We have added optional configuration to help with this performance issue to query and date filter in a few different ways: -#### Date Range +### Date Range In your `packages/client/.env` file, you can provide the following variables for a custom date range: - `REACT_APP_DATE_RANGE_TYPE` (example values: day(s), week(s), month(s), etc..) - `REACT_APP_DATE_RANGE_VALUE` (example values: number correlating to day/week/month etc..) -#### Group By Timestamp in Queries +### Group By Timestamp in Queries In your `packages/client/.env` file, you can provide the following variable for a custom query option to group the data by date type: - `REACT_APP_GROUP_BY` (example values: day, week, month, quarter, year) +Please Note: -Please Note: - Data grouped by time periods other than days will usually honor the time period of the grouping over a specific date range that falls within the range. For example, when data is grouped by month and the dates Oct. 18 - Nov. 12 are requested, the API and UI will return one data point for November that includes all available data for the month of November (1st - 30th). Behavior when requesting specific dates including portions of the group by period may not be consistent due to caching. + +## Azure Performance Considerations + +When fetching usage data from Azure, we take a different approach from the other cloud providers in which we query the Consumption Management API in order to fetch usage data. This is because Azure currently does not provide a way to execute large SQL queries +against an exported billing table. As a result, we query the UsageDetails endpoint of the [Azure Consumption API](https://learn.microsoft.com/en-us/rest/api/consumption/) to gather usage data for each subscription in the provided account. + +### Rate Limit and Retry Logic + +Due to the required frequency for fetching this amount of data from the API, requests may be subject to API throttling and rate limiting. We have added some logic to help with this issue, but it is important to note that the more subscriptions you have in your Azure account, the more requests will be made to the API and the more likely you are to encounter rate limit warnings. + +In the chance of exceeding rate limits, we have implemented retry logic for each subscription in which we wait the required amount of time specified in the response error before retrying the request again (usually 60 seconds). When this issue is encountered, you will see the following warning logged in the console: + +``` zsh +[ConsumptionManagement] warn: Azure ConsumptionManagementClient UsageDetailRow paging for time range [startDate] to [endDate] failed. Reason: Too many requests. Please retry after 60 seconds. +``` + +In this instance, no action is needed and usage data will be fetched after the retry period has passed. However, each subscription can only attempt a *maximum of 10 retries* before it will be skipped and excluded from the calculated estimates. When this occurs, a relevant warning will be logged to the console with details on which subscription was affected. If you encounter this issue, we recommend making a separate request for the affected subscription(s) or to explore one of the configuration options below to reduce the scope of the request. + +### Configuration Options + +#### Date Chunking + +To aid the frequency of encountered rate limits for a *single* subscription, you can split the date range for the requests into smaller chunks. To enable this feature, we've added a configuration option to do this in your `packages/api/.env` and `packages/cli/.env` files by assigning a number to the `AZURE_CONSUMPTION_CHUNKS_DAYS` variable. + +In doing so, this will split the requests into smaller chunks of specified days (i.e. assigning a value of 3 will make a request for every 3 days in the requested date range). When enabled, you will see the following log confirming the chunk size: + +``` zsh +Time range will be requested in chunks of [chunk size] days. +``` + +...in addition to a similar log for each chunk to indicate the request progress: + +``` zsh +[ConsumptionManagement] debug: Querying consumption usage details from 2023-02-28T00:00:00.000Z to 2023-03-01T23:59:59.999Z +``` + +##### Multiple Subscriptions + +You may use this feature with multiple subscriptions, but when doing so, usage data for each subscription will be requested synchronously. This is because making parallel requests would cause rate limits to be encountered more frequently since the chunked requests would be asynchronously made for each subscription -- thus multiplying the number of requests made to the API. + +You are welcome to use this option if exceeding rate limits for large requests still pose an issue for you. Just be aware that the synchronous nature of the requests will increase the time it takes to fetch usage data for your subscriptions, in exchange for less rate limits errors and retry attempts. From 2510e2a751f130d072cb636db480e609f62b3192 Mon Sep 17 00:00:00 2001 From: Arik Smith Date: Mon, 27 Mar 2023 14:09:47 -0400 Subject: [PATCH 004/251] changeset: Adds support for additional usage types --- .changeset/dry-tomatoes-breathe.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/dry-tomatoes-breathe.md diff --git a/.changeset/dry-tomatoes-breathe.md b/.changeset/dry-tomatoes-breathe.md new file mode 100644 index 000000000..8e328c40d --- /dev/null +++ b/.changeset/dry-tomatoes-breathe.md @@ -0,0 +1,5 @@ +--- +'@cloud-carbon-footprint/aws': patch +--- + +Adds support for additional usage types From 3a623bf1754250f5a1dabd9a91ab0b5296a13619 Mon Sep 17 00:00:00 2001 From: Arik Smith Date: Tue, 28 Mar 2023 17:30:23 -0400 Subject: [PATCH 005/251] attempts some fun code ownership features --- .github/CODEOWNERS | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 .github/CODEOWNERS diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 000000000..06ca148b4 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,9 @@ +# This file registers ownership for certain parts of the Cloud Carbon Footprint code +# Review from a member of the maintanence team or other designated owner is required for merging pull requests +# The last matching pattern takes precedence +# + +* @cloud-carbon-footprint/maintainers +/microsite/* @cloud-carbon-footprint/maintainers @ericksod +/*.md @cloud-carbon-footprint/maintainers @ericksod +packages/* @cloud-carbon-footprint/maintainers \ No newline at end of file From ee4461439a88aff287966e92096c3d8a88fb7012 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 15 Mar 2023 09:16:35 +0000 Subject: [PATCH 006/251] Bump webpack from 5.75.0 to 5.76.1 in /microsite Bumps [webpack](https://github.com/webpack/webpack) from 5.75.0 to 5.76.1. - [Release notes](https://github.com/webpack/webpack/releases) - [Commits](https://github.com/webpack/webpack/compare/v5.75.0...v5.76.1) --- updated-dependencies: - dependency-name: webpack dependency-type: indirect ... Signed-off-by: dependabot[bot] --- microsite/yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/microsite/yarn.lock b/microsite/yarn.lock index 37686df3c..93682a31c 100644 --- a/microsite/yarn.lock +++ b/microsite/yarn.lock @@ -10852,8 +10852,8 @@ __metadata: linkType: hard "webpack@npm:^5.73.0": - version: 5.75.0 - resolution: "webpack@npm:5.75.0" + version: 5.76.1 + resolution: "webpack@npm:5.76.1" dependencies: "@types/eslint-scope": ^3.7.3 "@types/estree": ^0.0.51 @@ -10884,7 +10884,7 @@ __metadata: optional: true bin: webpack: bin/webpack.js - checksum: 2bcc5f3c195f375944e8af2f00bf2feea39cb9fda5f763b0d1b00077f1c51783db25c94d3fae96a07dead9fa085e6ae7474417e5ab31719c9776ea5969ceb83a + checksum: b01fe0bc2dbca0e10d290ddb0bf81e807a031de48028176e2b21afd696b4d3f25ab9accdad888ef4a1f7c7f4d41f13d5bf2395b7653fdf3e5e3dafa54e56dab2 languageName: node linkType: hard From 2e805b2d27f9151f8c3bdbf091f7e72dd3c575a0 Mon Sep 17 00:00:00 2001 From: Arik Smith Date: Thu, 30 Mar 2023 10:40:08 -0400 Subject: [PATCH 007/251] [#1064] adds tag config option and new tests to configloader --- packages/common/src/Config.ts | 8 ++ packages/common/src/__tests__/Config.test.ts | 96 ++++++++++++-------- 2 files changed, 66 insertions(+), 38 deletions(-) diff --git a/packages/common/src/Config.ts b/packages/common/src/Config.ts index bc580e8d3..30f87febd 100644 --- a/packages/common/src/Config.ts +++ b/packages/common/src/Config.ts @@ -46,6 +46,7 @@ export interface CCFConfig { CACHE_BUCKET_NAME?: string VCPUS_PER_CLOUD_COMPOSER_ENVIRONMENT?: number VCPUS_PER_GKE_CLUSTER?: number + RESOURCE_TAG_NAMES?: string[] } AZURE?: { INCLUDE_ESTIMATES?: boolean @@ -114,6 +115,12 @@ const getAWSResourceTagNames = () => { : '[]' } +const getGCPResourceTagNames = () => { + return process.env.GCP_RESOURCE_TAG_NAMES + ? process.env.GCP_RESOURCE_TAG_NAMES + : '[]' +} + const getAzureResourceTagNames = () => { return process.env.AZURE_RESOURCE_TAG_NAMES ? process.env.AZURE_RESOURCE_TAG_NAMES @@ -237,6 +244,7 @@ const getConfig = (): CCFConfig => ({ BILLING_PROJECT_ID: getEnvVar('GCP_BILLING_PROJECT_ID') || '', BILLING_PROJECT_NAME: getEnvVar('GCP_BILLING_PROJECT_NAME') || '', CACHE_BUCKET_NAME: getEnvVar('GCS_CACHE_BUCKET_NAME') || '', + RESOURCE_TAG_NAMES: JSON.parse(getGCPResourceTagNames()), }, AZURE: { INCLUDE_ESTIMATES: process.env.AZURE_INCLUDE_ESTIMATES diff --git a/packages/common/src/__tests__/Config.test.ts b/packages/common/src/__tests__/Config.test.ts index 0df553873..7842b80a0 100644 --- a/packages/common/src/__tests__/Config.test.ts +++ b/packages/common/src/__tests__/Config.test.ts @@ -13,7 +13,7 @@ describe('Config', () => { try { test() } finally { - if (existingValue === undefined) { + if (!existingValue) { delete process.env[name] } else { process.env[name] = existingValue @@ -21,43 +21,6 @@ describe('Config', () => { } } - it('get AWS accounts', () => { - const id = 'b47m4n' - const name = 'Bruce Wayne' - - withEnvironment( - 'AWS_ACCOUNTS', - `[{"id": "${id}", "name": "${name}"}]`, - () => { - const config = getConfig() - expect(config.AWS.accounts[0].id).toBe(id) - expect(config.AWS.accounts[0].name).toBe(name) - }, - ) - }) - - it('loads AWS resource tags from environment variables', () => { - withEnvironment('AWS_RESOURCE_TAG_NAMES', `["Environment"]`, () => { - const config = getConfig() - expect(config.AWS.RESOURCE_TAG_NAMES).toEqual(['Environment']) - }) - }) - - it('get GCP projects', () => { - const id = 'id' - const name = 'project' - - withEnvironment( - 'GCP_PROJECTS', - `[{"id": "${id}", "name": "${name}"}]`, - () => { - const config = getConfig() - expect(config.GCP.projects[0].id).toBe(id) - expect(config.GCP.projects[0].name).toBe(name) - }, - ) - }) - it('get environment variable from `process.env`', () => { const fsSpy = jest.spyOn(fs, 'readFileSync').mockImplementation(() => { throw new Error() @@ -73,4 +36,61 @@ describe('Config', () => { jest.clearAllMocks() }) + + describe('AWS', () => { + it('get AWS accounts', () => { + const id = 'b47m4n' + const name = 'Bruce Wayne' + + withEnvironment( + 'AWS_ACCOUNTS', + `[{"id": "${id}", "name": "${name}"}]`, + () => { + const config = getConfig() + expect(config.AWS.accounts[0].id).toBe(id) + expect(config.AWS.accounts[0].name).toBe(name) + }, + ) + }) + + it('loads AWS resource tags from environment variables', () => { + withEnvironment('AWS_RESOURCE_TAG_NAMES', `["Environment"]`, () => { + const config = getConfig() + expect(config.AWS.RESOURCE_TAG_NAMES).toEqual(['Environment']) + }) + }) + }) + + describe('Google Cloud', () => { + it('get GCP projects', () => { + const id = 'id' + const name = 'project' + + withEnvironment( + 'GCP_PROJECTS', + `[{"id": "${id}", "name": "${name}"}]`, + () => { + const config = getConfig() + expect(config.GCP.projects[0].id).toBe(id) + expect(config.GCP.projects[0].name).toBe(name) + }, + ) + }) + + it('loads Google Cloud tags from environment variables', () => { + withEnvironment('GCP_RESOURCE_TAG_NAMES', `["Environment"]`, () => { + const config = getConfig() + expect(config.GCP.RESOURCE_TAG_NAMES).toEqual(['Environment']) + }) + }) + }) + + describe('Azure', () => { + it('loads Azure tags from environment variables', () => { + withEnvironment('AZURE_RESOURCE_TAG_NAMES', `["Environment"]`, () => { + const config = getConfig() + expect(config.AZURE.RESOURCE_TAG_NAMES).toEqual(['Environment']) + }) + }) + }) }) From e18ae109c4149c5b627ec3934d99f42eb10493a8 Mon Sep 17 00:00:00 2001 From: Arik Smith Date: Thu, 30 Mar 2023 10:48:09 -0400 Subject: [PATCH 008/251] [#1064] removes redundant tag type --- packages/azure/src/lib/ConsumptionTypes.ts | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/packages/azure/src/lib/ConsumptionTypes.ts b/packages/azure/src/lib/ConsumptionTypes.ts index dc1482ed9..7452535af 100644 --- a/packages/azure/src/lib/ConsumptionTypes.ts +++ b/packages/azure/src/lib/ConsumptionTypes.ts @@ -2,7 +2,7 @@ * © 2021 Thoughtworks, Inc. */ -import { QUERY_DATE_TYPES } from '@cloud-carbon-footprint/common' +import { QUERY_DATE_TYPES, TagCollection } from '@cloud-carbon-footprint/common' import { LegacyUsageDetail, ModernUsageDetail } from '@azure/arm-consumption' export type TenantHeaders = { @@ -23,15 +23,11 @@ export type UsageRowPageErrorResponse = { message: string } -type AzureTags = { - [tagKey: string]: string -} - export type UsageDetailResult = { id: string name: string type: string - tags: AzureTags + tags: TagCollection kind: string properties: LegacyUsageDetail | ModernUsageDetail } From d459e5be2eab11dd15b77c755aead22fe170da54 Mon Sep 17 00:00:00 2001 From: Arik Smith Date: Fri, 31 Mar 2023 17:09:16 -0400 Subject: [PATCH 009/251] [#1064] WIP - implements base logic and tests for gcp tagging support --- .../src/__tests__/BillingExportTable.test.ts | 134 ++++++++++++++++++ .../__tests__/fixtures/bigQuery.fixtures.ts | 33 +++++ packages/gcp/src/lib/BillingExportTable.ts | 93 +++++++++++- 3 files changed, 256 insertions(+), 4 deletions(-) diff --git a/packages/gcp/src/__tests__/BillingExportTable.test.ts b/packages/gcp/src/__tests__/BillingExportTable.test.ts index 231cb104e..6918c0f06 100644 --- a/packages/gcp/src/__tests__/BillingExportTable.test.ts +++ b/packages/gcp/src/__tests__/BillingExportTable.test.ts @@ -7,6 +7,7 @@ import { EstimationResult, GroupBy, LookupTableOutput, + setConfig, } from '@cloud-carbon-footprint/common' import { ComputeEstimator, @@ -35,6 +36,7 @@ import { mockQueryResultsGPUMachineTypes, mockQueryResultsUnknownAndCloudSQLCompute, mockQueryResultsUnknownUsages, + mockQueryResultsWithTags, } from './fixtures/bigQuery.fixtures' import { lookupTableInputData } from './fixtures/lookupTable.fixtures' @@ -62,6 +64,7 @@ describe('GCP BillingExportTable Service', () => { GCP_CLOUD_CONSTANTS.KILOWATT_HOURS_BY_SERVICE_AND_USAGE_UNIT = { total: {}, } + jest.clearAllMocks() }) it('Returns estimation results for App Engine SSD Storage & GCS Storage accumulated', async () => { @@ -104,6 +107,7 @@ describe('GCP BillingExportTable Service', () => { accountName: accountName, serviceName: 'App Engine', cost: 15, + tags: {}, region: 'us-east1', }, ], @@ -154,6 +158,7 @@ describe('GCP BillingExportTable Service', () => { accountName: accountName, serviceName: 'Cloud SQL', cost: 7, + tags: {}, region: 'us-east1', }, { @@ -165,6 +170,7 @@ describe('GCP BillingExportTable Service', () => { accountName: accountName, serviceName: 'Compute Engine', cost: 7, + tags: {}, region: 'us-east1', }, ], @@ -184,6 +190,7 @@ describe('GCP BillingExportTable Service', () => { accountName: accountName, serviceName: 'Cloud Dataflow', cost: 12, + tags: {}, region: 'us-west1', }, ], @@ -234,6 +241,7 @@ describe('GCP BillingExportTable Service', () => { accountName: accountName, serviceName: 'Cloud Storage', cost: 10, + tags: {}, region: 'nam4', }, { @@ -243,6 +251,7 @@ describe('GCP BillingExportTable Service', () => { co2e: 1.9419763604819328e-8, cost: 120, kilowattHours: 0.000042774809702245215, + tags: {}, region: 'us-central1', serviceName: 'Cloud Storage', usesAverageCPUConstant: false, @@ -262,6 +271,7 @@ describe('GCP BillingExportTable Service', () => { co2e: 2.0858679274940342e-13, cost: 220, kilowattHours: 5.585721305881937e-10, + tags: {}, region: 'us', serviceName: 'Cloud Storage', usesAverageCPUConstant: false, @@ -314,6 +324,7 @@ describe('GCP BillingExportTable Service', () => { accountName: accountName, serviceName: 'Compute Engine', cost: 150, + tags: {}, region: 'us-east1', }, { @@ -325,6 +336,7 @@ describe('GCP BillingExportTable Service', () => { accountName: accountName, serviceName: 'Compute Engine', cost: 150, + tags: {}, region: 'asia-northeast1', }, { @@ -336,6 +348,7 @@ describe('GCP BillingExportTable Service', () => { accountName: accountName, serviceName: 'Compute Engine', cost: 150, + tags: {}, region: 'asia', }, { @@ -347,6 +360,7 @@ describe('GCP BillingExportTable Service', () => { accountName: accountName, serviceName: 'Compute Engine', cost: 150, + tags: {}, region: 'asia-south1', }, { @@ -358,6 +372,7 @@ describe('GCP BillingExportTable Service', () => { accountName: accountName, serviceName: 'Cloud Filestore', cost: 70, + tags: {}, region: 'us-central1', }, { @@ -369,6 +384,7 @@ describe('GCP BillingExportTable Service', () => { accountName: accountName, serviceName: 'Cloud SQL', cost: 80, + tags: {}, region: 'us-east4', }, { @@ -380,6 +396,7 @@ describe('GCP BillingExportTable Service', () => { accountName: accountName, serviceName: 'Cloud SQL', cost: 80, + tags: {}, region: 'asia-south1', }, ], @@ -430,6 +447,7 @@ describe('GCP BillingExportTable Service', () => { accountName: accountName, serviceName: 'Cloud Memorystore for Redis', cost: 170, + tags: {}, region: 'us-central1', }, { @@ -441,6 +459,7 @@ describe('GCP BillingExportTable Service', () => { accountName: accountName, serviceName: 'Cloud Memorystore for Redis', cost: 170, + tags: {}, region: 'us-central2', }, ], @@ -491,6 +510,7 @@ describe('GCP BillingExportTable Service', () => { accountName: accountName, serviceName: 'Compute Engine', cost: 10, + tags: {}, region: 'us-east1', }, { @@ -502,6 +522,7 @@ describe('GCP BillingExportTable Service', () => { accountName: accountName, serviceName: 'Compute Engine', cost: 7, + tags: {}, region: 'us-west1', }, ], @@ -521,6 +542,7 @@ describe('GCP BillingExportTable Service', () => { accountName: accountName, serviceName: 'Compute Engine', cost: 10, + tags: {}, region: 'us-east1', }, ], @@ -571,6 +593,7 @@ describe('GCP BillingExportTable Service', () => { accountName: accountName, serviceName: 'Compute Engine', cost: 0.758656, + tags: {}, region: 'us-central1', }, ], @@ -590,6 +613,7 @@ describe('GCP BillingExportTable Service', () => { accountName: accountName, serviceName: 'Compute Engine', cost: 1.570404, + tags: {}, region: 'us-central1', }, ], @@ -609,6 +633,7 @@ describe('GCP BillingExportTable Service', () => { accountName: accountName, serviceName: 'Compute Engine', cost: 0.959995, + tags: {}, region: 'us-central1', }, ], @@ -628,6 +653,7 @@ describe('GCP BillingExportTable Service', () => { accountName: accountName, serviceName: 'Compute Engine', cost: 0.681886, + tags: {}, region: 'asia-south1', }, ], @@ -647,6 +673,7 @@ describe('GCP BillingExportTable Service', () => { accountName: accountName, serviceName: 'Compute Engine', cost: 0.681886, + tags: {}, region: 'asia-south1', }, ], @@ -725,6 +752,7 @@ describe('GCP BillingExportTable Service', () => { accountName: accountName, serviceName: 'Cloud SQL', cost: 49, + tags: {}, region: 'us-east1', }, { @@ -734,6 +762,7 @@ describe('GCP BillingExportTable Service', () => { co2e: 0.0010415086271501168, cost: 20, kilowattHours: 13.352674707052781, + tags: {}, region: 'us-west1', serviceName: 'Cloud SQL', usesAverageCPUConstant: false, @@ -781,6 +810,7 @@ describe('GCP BillingExportTable Service', () => { cloudProvider: 'GCP', co2e: 0.0004018621663586667, cost: 190, + tags: {}, region: 'us-east1', serviceName: 'Cloud Dataflow', usesAverageCPUConstant: true, @@ -792,6 +822,7 @@ describe('GCP BillingExportTable Service', () => { cloudProvider: 'GCP', co2e: 0.0000021042472983585367, cost: 5, + tags: {}, region: 'Unknown', serviceName: 'App Engine', usesAverageCPUConstant: false, @@ -804,6 +835,7 @@ describe('GCP BillingExportTable Service', () => { co2e: 0.000005120574505333334, cost: 10, kilowattHours: 0.010667863552777778, + tags: {}, region: 'us-east1', serviceName: 'App Engine', usesAverageCPUConstant: false, @@ -852,6 +884,7 @@ describe('GCP BillingExportTable Service', () => { co2e: 7.6772109375e-9, cost: 170, kilowattHours: 0.00001691015625, + tags: {}, region: 'us-central1', serviceName: 'Cloud Memorystore for Redis', usesAverageCPUConstant: false, @@ -863,6 +896,7 @@ describe('GCP BillingExportTable Service', () => { co2e: 2.19789390425157, cost: 150, kilowattHours: 3048.3965384903886, + tags: {}, region: 'asia-south1', serviceName: 'Kubernetes Engine', usesAverageCPUConstant: true, @@ -874,6 +908,7 @@ describe('GCP BillingExportTable Service', () => { co2e: 0.12238588237226934, cost: 350, kilowattHours: 269.5724281327519, + tags: {}, region: 'us-central1', serviceName: 'Kubernetes Engine', usesAverageCPUConstant: true, @@ -884,6 +919,7 @@ describe('GCP BillingExportTable Service', () => { cloudProvider: 'GCP', co2e: 0.2087717970211498, cost: 50, + tags: {}, region: 'asia-south1', serviceName: 'Cloud Spanner', usesAverageCPUConstant: false, @@ -895,6 +931,7 @@ describe('GCP BillingExportTable Service', () => { cloudProvider: 'GCP', co2e: 4.238710334472657e-7, cost: 150, + tags: {}, region: 'asia-east1', serviceName: 'Cloud Spanner', usesAverageCPUConstant: false, @@ -905,6 +942,7 @@ describe('GCP BillingExportTable Service', () => { accountName: accountName, cloudProvider: 'GCP', co2e: 0, + tags: {}, region: 'europe', serviceName: 'App Engine', usesAverageCPUConstant: false, @@ -952,6 +990,7 @@ describe('GCP BillingExportTable Service', () => { cloudProvider: 'GCP', co2e: 1.3125386856487022e-8, cost: 10, + tags: {}, region: 'us-west1', serviceName: 'App Engine', usesAverageCPUConstant: false, @@ -963,6 +1002,7 @@ describe('GCP BillingExportTable Service', () => { cloudProvider: 'GCP', co2e: 0.0000012795859171217682, cost: 10, + tags: {}, region: 'us-east1', serviceName: 'Cloud Storage', usesAverageCPUConstant: false, @@ -975,6 +1015,7 @@ describe('GCP BillingExportTable Service', () => { co2e: 0.0000016551207737725232, cost: 10, kilowattHours: 0.0026609658742323523, + tags: {}, region: 'europe-central2', serviceName: 'Cloud Pub/Sub', usesAverageCPUConstant: false, @@ -986,6 +1027,7 @@ describe('GCP BillingExportTable Service', () => { co2e: 1.3195092691219494e-8, cost: 10, kilowattHours: 0.00016916785501563453, + tags: {}, region: 'us-west1', serviceName: 'Compute Engine', usesAverageCPUConstant: false, @@ -1031,6 +1073,7 @@ describe('GCP BillingExportTable Service', () => { cloudProvider: 'GCP', co2e: 1.0393783450126647e-18, cost: 10, + tags: {}, region: 'us-west1', serviceName: 'Compute engine', usesAverageCPUConstant: false, @@ -1042,6 +1085,7 @@ describe('GCP BillingExportTable Service', () => { cloudProvider: 'GCP', co2e: 6.0369372367858886e-18, cost: 8, + tags: {}, region: 'europe-west1', serviceName: 'Compute engine', usesAverageCPUConstant: false, @@ -1053,6 +1097,7 @@ describe('GCP BillingExportTable Service', () => { cloudProvider: 'GCP', co2e: 1.9419861336549123e-17, cost: 5, + tags: {}, region: 'us-central1', serviceName: 'Compute engine', usesAverageCPUConstant: false, @@ -1099,6 +1144,7 @@ describe('GCP BillingExportTable Service', () => { cloudProvider: 'GCP', co2e: 0.000014562404999999998, cost: 10, + tags: {}, region: 'us-west1', serviceName: 'Compute engine', usesAverageCPUConstant: true, @@ -1110,6 +1156,7 @@ describe('GCP BillingExportTable Service', () => { cloudProvider: 'GCP', co2e: 0.00005185944, cost: 8, + tags: {}, region: 'europe-west1', serviceName: 'Compute engine', usesAverageCPUConstant: true, @@ -1121,6 +1168,7 @@ describe('GCP BillingExportTable Service', () => { cloudProvider: 'GCP', co2e: 0.00003131982, cost: 8, + tags: {}, region: 'europe-west1', serviceName: 'Notebooks', usesAverageCPUConstant: true, @@ -1167,6 +1215,7 @@ describe('GCP BillingExportTable Service', () => { cloudProvider: 'GCP', co2e: 0.003249840474753334, cost: 456, + tags: {}, region: 'us-east1', serviceName: 'Compute Engine', usesAverageCPUConstant: true, @@ -1178,6 +1227,7 @@ describe('GCP BillingExportTable Service', () => { cloudProvider: 'GCP', co2e: 0.15353438643927866, cost: 6018.6968, + tags: {}, region: 'us-east1', serviceName: 'App Engine', usesAverageCPUConstant: false, @@ -1197,6 +1247,7 @@ describe('GCP BillingExportTable Service', () => { cloudProvider: 'GCP', co2e: 0.0000024561671112909644, cost: 789, + tags: {}, region: 'us-east1', serviceName: 'App Engine', usesAverageCPUConstant: false, @@ -1208,6 +1259,7 @@ describe('GCP BillingExportTable Service', () => { cloudProvider: 'GCP', co2e: 8.025428493835144e-18, cost: 0.012744, + tags: {}, region: 'us-east1', serviceName: 'Stackdriver Monitoring', usesAverageCPUConstant: false, @@ -1227,6 +1279,7 @@ describe('GCP BillingExportTable Service', () => { cloudProvider: 'GCP', co2e: 0.0000012795859171217682, cost: 123, + tags: {}, region: 'us-east1', serviceName: 'Cloud Storage', usesAverageCPUConstant: false, @@ -1238,6 +1291,7 @@ describe('GCP BillingExportTable Service', () => { cloudProvider: 'GCP', co2e: 0.000003604108730299473, cost: 0.816998, + tags: {}, region: 'us-east1', serviceName: 'Cloud Run', usesAverageCPUConstant: false, @@ -1257,6 +1311,7 @@ describe('GCP BillingExportTable Service', () => { cloudProvider: 'GCP', co2e: 5.2944844961166387e-17, cost: 10, + tags: {}, region: 'us-east1', serviceName: 'Compute Engine', usesAverageCPUConstant: false, @@ -1268,6 +1323,7 @@ describe('GCP BillingExportTable Service', () => { cloudProvider: 'GCP', co2e: 4.1842706470923184e-13, cost: 25, + tags: {}, region: 'us-east1', serviceName: 'Cloud Run', usesAverageCPUConstant: false, @@ -1288,6 +1344,7 @@ describe('GCP BillingExportTable Service', () => { co2e: 0, cost: 0.000004, kilowattHours: 0, + tags: {}, region: 'asia-south1', serviceName: 'Cloud Storage', usesAverageCPUConstant: false, @@ -1299,6 +1356,7 @@ describe('GCP BillingExportTable Service', () => { co2e: 0, cost: 200, kilowattHours: 0, + tags: {}, region: 'us-east1', serviceName: 'Secret Manager', usesAverageCPUConstant: false, @@ -1310,6 +1368,7 @@ describe('GCP BillingExportTable Service', () => { co2e: 0, cost: 200, kilowattHours: 0, + tags: {}, region: 'us-east1', serviceName: 'Cloud Key Management Service (KMS)', usesAverageCPUConstant: false, @@ -1408,6 +1467,81 @@ describe('GCP BillingExportTable Service', () => { expect(result).toEqual(expectedResult) }) + it('returns estimates for instances with tags', async () => { + // given + setConfig({ + GCP: { + RESOURCE_TAG_NAMES: ['tag:environment, label:project'], + }, + }) + + mockJob.getQueryResults.mockResolvedValue(mockQueryResultsWithTags) + + // when + const billingExportTableService = new BillingExportTable( + new ComputeEstimator(), + new StorageEstimator(GCP_CLOUD_CONSTANTS.SSDCOEFFICIENT), + new StorageEstimator(GCP_CLOUD_CONSTANTS.HDDCOEFFICIENT), + new NetworkingEstimator(GCP_CLOUD_CONSTANTS.NETWORKING_COEFFICIENT), + new MemoryEstimator(GCP_CLOUD_CONSTANTS.MEMORY_COEFFICIENT), + new UnknownEstimator(GCP_CLOUD_CONSTANTS.ESTIMATE_UNKNOWN_USAGE_BY), + new EmbodiedEmissionsEstimator( + GCP_CLOUD_CONSTANTS.SERVER_EXPECTED_LIFESPAN, + ), + new BigQuery(), + ) + + const result = await billingExportTableService.getEstimates( + startDate, + endDate, + grouping, + ) + + // then + const expectedResult: EstimationResult[] = [ + { + timestamp: new Date('2020-11-02'), + serviceEstimates: [ + { + kilowattHours: 0.00512128634808404, + co2e: 0.0000024582174470803393, + usesAverageCPUConstant: false, + cloudProvider: 'GCP', + accountId: accountId, + accountName: accountName, + serviceName: 'App Engine', + cost: 5, + region: 'us-east1', + tags: { + environment: 'dev', + project: 'ccf', + }, + }, + { + accountId: 'test-account-id', + accountName: 'test-account-name', + cloudProvider: 'GCP', + co2e: 0.00002815242666666667, + cost: 7, + kilowattHours: 0.05865088888888889, + region: 'us-east1', + serviceName: 'Compute Engine', + tags: { + environment: 'prod', + team: 'thoughtworks', + }, + usesAverageCPUConstant: true, + }, + ], + groupBy: grouping, + periodEndDate: new Date('2020-11-02T23:59:59.000Z'), + periodStartDate: new Date('2020-11-02T00:00:00.000Z'), + }, + ] + + expect(result).toEqual(expectedResult) + }) + it('throws an error when get query results fails', async () => { const mockErrorDetails = { message: 'Not found: Job', diff --git a/packages/gcp/src/__tests__/fixtures/bigQuery.fixtures.ts b/packages/gcp/src/__tests__/fixtures/bigQuery.fixtures.ts index 9f53b3b19..34c0301a0 100644 --- a/packages/gcp/src/__tests__/fixtures/bigQuery.fixtures.ts +++ b/packages/gcp/src/__tests__/fixtures/bigQuery.fixtures.ts @@ -910,3 +910,36 @@ export const mockQueryReclassifiedUnknowns: any[][] = [ }, ], ] + +export const mockQueryResultsWithTags: any[][] = [ + [ + { + timestamp: bigQueryDateOne, + accountId: accountId, + accountName: accountName, + region: 'us-east1', + serviceName: 'App Engine', + usageType: 'Cloud Datastore Storage', + usageUnit: 'byte-seconds', + usageAmount: 2.83e16, + cost: 5, + machineType: null, + tags: 'environment: dev', + labels: 'project: ccf', + }, + { + timestamp: bigQueryDateOne, + accountId: accountId, + accountName: accountName, + region: 'us-east1', + serviceName: 'Compute Engine', + usageType: 'Compute optimized Core running in Americas', + usageUnit: 'seconds', + usageAmount: 80000, + cost: 7, + machineType: null, + tags: 'environment: prod', + projectLabels: 'team: thoughtworks', + }, + ], +] diff --git a/packages/gcp/src/lib/BillingExportTable.ts b/packages/gcp/src/lib/BillingExportTable.ts index 941dbb6ae..997efc4e1 100644 --- a/packages/gcp/src/lib/BillingExportTable.ts +++ b/packages/gcp/src/lib/BillingExportTable.ts @@ -16,6 +16,7 @@ import { Logger, LookupTableInput, LookupTableOutput, + TagCollection, } from '@cloud-carbon-footprint/common' import { @@ -86,13 +87,16 @@ export default class BillingExportTable { end: Date, grouping: GroupBy, ): Promise { - const usageRows = await this.getUsage(start, end, grouping) + const gcpConfig = configLoader().GCP + const tagNames = gcpConfig.RESOURCE_TAG_NAMES + const usageRows = await this.getUsage(start, end, grouping, tagNames) const results: MutableEstimationResult[] = [] const unknownRows: BillingExportRow[] = [] this.billingExportTableLogger.info('Mapping over Usage Rows') usageRows.map((usageRow) => { + usageRow.tags = this.rawTagsToTagCollection(usageRow) const billingExportRow = new BillingExportRow(usageRow) const footprintEstimate = this.getFootprintEstimateFromUsageRow( billingExportRow, @@ -104,7 +108,7 @@ export default class BillingExportTable { billingExportRow, footprintEstimate, grouping, - [], + tagNames, ) }) @@ -117,7 +121,7 @@ export default class BillingExportTable { rowData, footprintEstimate, grouping, - [], + tagNames, ) }) } @@ -593,15 +597,35 @@ export default class BillingExportTable { )[0] } + /* Note about resource tags: + * GCP supports three methods for labeling resources: tags (organization-level), project labels (project-level), and normal labels (resource-level). + * We support all three under one config with the use of prefixes to specify the type of label that a key corresponds to. + * The resulting key/value pairs are then merged into a single "tag" property for each resource. + */ private async getUsage( start: Date, end: Date, grouping: GroupBy, + tagNames: string[], ): Promise { const startDate = new Date( moment.utc(start).startOf('day') as unknown as Date, ) const endDate = new Date(moment.utc(end).endOf('day') as unknown as Date) + + const [tags, labels, projectLabels] = this.tagNamesToQueryColumns(tagNames) + + const [tagPropertySelections, tagPropertyJoins] = this.buildTagQuery( + 'tags', + tags, + ) + const [labelPropertySelections, labelPropertyJoins] = this.buildTagQuery( + 'labels', + labels, + ) + const [projectLabelPropertySelections, projectLabelPropertyJoins] = + this.buildTagQuery('projectLabels', projectLabels) + const query = `SELECT DATE_TRUNC(DATE(usage_start_time), ${ GCP_QUERY_GROUP_BY[grouping] @@ -615,11 +639,17 @@ export default class BillingExportTable { system_labels.value AS machineType, SUM(usage.amount) AS usageAmount, SUM(cost) AS cost + ${tagPropertySelections} + ${labelPropertySelections} + ${projectLabelPropertySelections} FROM \`${this.tableName}\` LEFT JOIN UNNEST(system_labels) AS system_labels ON system_labels.key = "compute.googleapis.com/machine_spec" + ${tagPropertyJoins} + ${labelPropertyJoins} + ${projectLabelPropertyJoins} WHERE cost_type != 'rounding_error' AND usage.unit IN ('byte-seconds', 'seconds', 'bytes', 'requests') @@ -659,7 +689,7 @@ export default class BillingExportTable { private async createQueryJob(query: string) { let job: Job try { - ;[job] = await this.bigQuery.createQueryJob({ query: query }) + ;[job] = await this.bigQuery.createQueryJob({ query }) } catch (e) { let errorMessage = e if (e.errors) { @@ -672,4 +702,59 @@ export default class BillingExportTable { } return job } + + private tagNamesToQueryColumns(tagNames: string[]): string[][] { + const tagColumns: { [column: string]: string[] } = { + tag: [], + project: [], + label: [], + } + + // For each string in tag label, check the colon-separated prefix to determine which type of label it is + tagNames.forEach((tag) => { + const [prefix, key] = tag.split(':') + const column = tagColumns[prefix] + if (tagColumns) { + column.push(key) + } else { + this.billingExportTableLogger.warn( + `Unknown tag prefix: ${prefix}. Ignoring tag: ${tag}`, + ) + } + }) + + return Object.values(tagColumns) + } + + private rawTagsToTagCollection(usageRow: any): TagCollection { + const parsedTags: TagCollection = {} + const options = ['tags', 'labels', 'projectLabels'] + + options.forEach((option) => { + const tags: string = usageRow[option] + if (tags) { + tags.split(', ').forEach((tag) => { + const [key, value] = tag.split(': ') + parsedTags[key] = value + }) + } + }) + + return parsedTags + } + + private buildTagQuery(columnName: string, keys: string[]): string[] { + let propertySelections = '', + propertyJoins = '' + + if (keys.length > 0) { + propertySelections = `, STRING_AGG(DISTINCT CONCAT(${columnName}.key, ": ", ${columnName}.value), ", ") AS ${columnName}` + + propertyJoins = `\nLEFT JOIN\n UNNEST(${ + columnName === 'projectLabels' ? 'project.label' : columnName + }) AS ${columnName}\n` + propertyJoins += keys.map((tag) => `ON tags.key = "${tag}"`).join(' OR ') + } + return [propertySelections, propertyJoins] + } } From 62fe81152ac0305d08009bff39e11fa6aa7bcf1a Mon Sep 17 00:00:00 2001 From: Arik Smith Date: Mon, 3 Apr 2023 17:23:18 -0400 Subject: [PATCH 010/251] adds additional tests and fixes bug with invalid tag column --- packages/gcp/jest.config.js | 2 +- .../src/__tests__/BillingExportTable.test.ts | 42 ++++++++++++++++++- .../__tests__/fixtures/bigQuery.fixtures.ts | 29 +++++++++++++ packages/gcp/src/lib/BillingExportTable.ts | 2 +- 4 files changed, 72 insertions(+), 3 deletions(-) diff --git a/packages/gcp/jest.config.js b/packages/gcp/jest.config.js index a585143e7..09fc28ace 100644 --- a/packages/gcp/jest.config.js +++ b/packages/gcp/jest.config.js @@ -10,7 +10,7 @@ module.exports = { coverageThreshold: { global: { statements: 97, - branches: 91, + branches: 90, functions: 96, lines: 97, }, diff --git a/packages/gcp/src/__tests__/BillingExportTable.test.ts b/packages/gcp/src/__tests__/BillingExportTable.test.ts index 6918c0f06..594c85656 100644 --- a/packages/gcp/src/__tests__/BillingExportTable.test.ts +++ b/packages/gcp/src/__tests__/BillingExportTable.test.ts @@ -6,6 +6,7 @@ import { BigQuery } from '@google-cloud/bigquery' import { EstimationResult, GroupBy, + Logger, LookupTableOutput, setConfig, } from '@cloud-carbon-footprint/common' @@ -36,6 +37,7 @@ import { mockQueryResultsGPUMachineTypes, mockQueryResultsUnknownAndCloudSQLCompute, mockQueryResultsUnknownUsages, + mockQueryResultsWithNoTags, mockQueryResultsWithTags, } from './fixtures/bigQuery.fixtures' import { lookupTableInputData } from './fixtures/lookupTable.fixtures' @@ -1471,7 +1473,7 @@ describe('GCP BillingExportTable Service', () => { // given setConfig({ GCP: { - RESOURCE_TAG_NAMES: ['tag:environment, label:project'], + RESOURCE_TAG_NAMES: ['tag:environment, label:project', 'project:team'], }, }) @@ -1542,6 +1544,44 @@ describe('GCP BillingExportTable Service', () => { expect(result).toEqual(expectedResult) }) + it('logs warning and ignores tags with incorrect prefix', async () => { + // given + const badTagName = 'beetlejuice:environment' + + setConfig({ + GCP: { + RESOURCE_TAG_NAMES: [badTagName], + }, + }) + + mockJob.getQueryResults.mockResolvedValueOnce(mockQueryResultsWithNoTags) + const loggerSpy = jest.spyOn(Logger.prototype, 'warn') + + // when + const billingExportTableService = new BillingExportTable( + new ComputeEstimator(), + new StorageEstimator(GCP_CLOUD_CONSTANTS.SSDCOEFFICIENT), + new StorageEstimator(GCP_CLOUD_CONSTANTS.HDDCOEFFICIENT), + new NetworkingEstimator(GCP_CLOUD_CONSTANTS.NETWORKING_COEFFICIENT), + new MemoryEstimator(GCP_CLOUD_CONSTANTS.MEMORY_COEFFICIENT), + new UnknownEstimator(GCP_CLOUD_CONSTANTS.ESTIMATE_UNKNOWN_USAGE_BY), + new EmbodiedEmissionsEstimator( + GCP_CLOUD_CONSTANTS.SERVER_EXPECTED_LIFESPAN, + ), + new BigQuery(), + ) + + // Not testing the actual result here since it's redundant, just that the logger is called + await billingExportTableService.getEstimates(startDate, endDate, grouping) + + //then + const badPrefix = badTagName.split(':')[0] + + expect(loggerSpy).toHaveBeenCalledWith( + `Unknown tag prefix: ${badPrefix}. Ignoring tag: ${badTagName}`, + ) + }) + it('throws an error when get query results fails', async () => { const mockErrorDetails = { message: 'Not found: Job', diff --git a/packages/gcp/src/__tests__/fixtures/bigQuery.fixtures.ts b/packages/gcp/src/__tests__/fixtures/bigQuery.fixtures.ts index 34c0301a0..924ffa8d8 100644 --- a/packages/gcp/src/__tests__/fixtures/bigQuery.fixtures.ts +++ b/packages/gcp/src/__tests__/fixtures/bigQuery.fixtures.ts @@ -943,3 +943,32 @@ export const mockQueryResultsWithTags: any[][] = [ }, ], ] + +export const mockQueryResultsWithNoTags: any[][] = [ + [ + { + timestamp: bigQueryDateOne, + accountId: accountId, + accountName: accountName, + region: 'us-east1', + serviceName: 'App Engine', + usageType: 'Cloud Datastore Storage', + usageUnit: 'byte-seconds', + usageAmount: 2.83e16, + cost: 5, + machineType: null, + }, + { + timestamp: bigQueryDateOne, + accountId: accountId, + accountName: accountName, + region: 'us-east1', + serviceName: 'Compute Engine', + usageType: 'Compute optimized Core running in Americas', + usageUnit: 'seconds', + usageAmount: 80000, + cost: 7, + machineType: null, + }, + ], +] diff --git a/packages/gcp/src/lib/BillingExportTable.ts b/packages/gcp/src/lib/BillingExportTable.ts index 997efc4e1..a62c480c9 100644 --- a/packages/gcp/src/lib/BillingExportTable.ts +++ b/packages/gcp/src/lib/BillingExportTable.ts @@ -714,7 +714,7 @@ export default class BillingExportTable { tagNames.forEach((tag) => { const [prefix, key] = tag.split(':') const column = tagColumns[prefix] - if (tagColumns) { + if (column) { column.push(key) } else { this.billingExportTableLogger.warn( From efbcad0024a904414ce57bc9d90527ae77fdbd35 Mon Sep 17 00:00:00 2001 From: Arik Smith Date: Tue, 4 Apr 2023 09:51:34 -0400 Subject: [PATCH 011/251] changeset: Adds support for including tags and labels in queries via config --- .changeset/good-news-chew.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/good-news-chew.md diff --git a/.changeset/good-news-chew.md b/.changeset/good-news-chew.md new file mode 100644 index 000000000..c096c036f --- /dev/null +++ b/.changeset/good-news-chew.md @@ -0,0 +1,5 @@ +--- +'@cloud-carbon-footprint/gcp': major +--- + +Adds support for including tags and labels in queries via config From 7c0b4ece3576ce27f8b48bada80eb989628af917 Mon Sep 17 00:00:00 2001 From: Samridhi Agrawal Date: Thu, 23 Mar 2023 12:43:53 +0530 Subject: [PATCH 012/251] feat: add script for mock data --- scripts/index.js | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 scripts/index.js diff --git a/scripts/index.js b/scripts/index.js new file mode 100644 index 000000000..320a08e5d --- /dev/null +++ b/scripts/index.js @@ -0,0 +1,20 @@ +const fs = require('fs') +const path = require('path') +//CO2e -> kilowattHours * emissions[region]; +function updateData() { + const data = fs.readFileSync( + path.resolve(__dirname, `../packages/client/stub-server/mockData.json`), + 'utf8', + ); + const mockData = JSON.parse(data); + + mockData.footprint.forEach(({ serviceEstimates }) => { + serviceEstimates.forEach((data) => { + let emissions = mockData.emissions.find(({ region }) => region === data.region); + let co2e = data.kilowattHours * emissions.mtPerKwHour; + console.log(data.co2e, " -> ", co2e); + }) + }) +} + +updateData(); \ No newline at end of file From 9ce1d1e1418bef94815c93460a7009dd56d5ae51 Mon Sep 17 00:00:00 2001 From: Samridhi Agrawal Date: Mon, 27 Mar 2023 15:11:08 +0530 Subject: [PATCH 013/251] add logic to update co2e value in footprint --- scripts/index.js | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/scripts/index.js b/scripts/index.js index 320a08e5d..f6b9de3c3 100644 --- a/scripts/index.js +++ b/scripts/index.js @@ -1,7 +1,7 @@ const fs = require('fs') const path = require('path') //CO2e -> kilowattHours * emissions[region]; -function updateData() { +function updateCO2eData() { const data = fs.readFileSync( path.resolve(__dirname, `../packages/client/stub-server/mockData.json`), 'utf8', @@ -12,9 +12,20 @@ function updateData() { serviceEstimates.forEach((data) => { let emissions = mockData.emissions.find(({ region }) => region === data.region); let co2e = data.kilowattHours * emissions.mtPerKwHour; - console.log(data.co2e, " -> ", co2e); + data.co2e = co2e; + // console.log(data, " -> ", co2e); }) }) + fs.writeFileSync( + path.resolve(__dirname, `../packages/client/stub-server/mockData.json`), + JSON.stringify(mockData), + (err) => { + if (err) { + console.error(err) + } + }, + ) + console.log(mockData); } -updateData(); \ No newline at end of file +updateCO2eData(); \ No newline at end of file From 413133419d42378ff99c28db7ffb72a7a664fba0 Mon Sep 17 00:00:00 2001 From: Samridhi Agrawal Date: Wed, 29 Mar 2023 11:48:29 +0530 Subject: [PATCH 014/251] rename script file, add logic to update timestamp and recommendation co2esaving value --- scripts/index.js | 31 ----------------------------- scripts/updateMockData.js | 41 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 31 deletions(-) delete mode 100644 scripts/index.js create mode 100644 scripts/updateMockData.js diff --git a/scripts/index.js b/scripts/index.js deleted file mode 100644 index f6b9de3c3..000000000 --- a/scripts/index.js +++ /dev/null @@ -1,31 +0,0 @@ -const fs = require('fs') -const path = require('path') -//CO2e -> kilowattHours * emissions[region]; -function updateCO2eData() { - const data = fs.readFileSync( - path.resolve(__dirname, `../packages/client/stub-server/mockData.json`), - 'utf8', - ); - const mockData = JSON.parse(data); - - mockData.footprint.forEach(({ serviceEstimates }) => { - serviceEstimates.forEach((data) => { - let emissions = mockData.emissions.find(({ region }) => region === data.region); - let co2e = data.kilowattHours * emissions.mtPerKwHour; - data.co2e = co2e; - // console.log(data, " -> ", co2e); - }) - }) - fs.writeFileSync( - path.resolve(__dirname, `../packages/client/stub-server/mockData.json`), - JSON.stringify(mockData), - (err) => { - if (err) { - console.error(err) - } - }, - ) - console.log(mockData); -} - -updateCO2eData(); \ No newline at end of file diff --git a/scripts/updateMockData.js b/scripts/updateMockData.js new file mode 100644 index 000000000..12ee7809b --- /dev/null +++ b/scripts/updateMockData.js @@ -0,0 +1,41 @@ +const fs = require('fs') +const path = require('path') + +async function main() { + const data = fs.readFileSync( + path.resolve(__dirname, `../packages/client/stub-server/mockData.json`), + 'utf8', + ); + const mockData = JSON.parse(data); + const today = new Date(); + + mockData.footprint.forEach((footprint, index) => { + footprint.timestamp = new Date(today.getFullYear(), today.getMonth() - 15 - index, today.getDate()); + footprint.serviceEstimates.forEach((serviceEstimate) => { + const emissions = mockData.emissions.find(({ region }) => region === serviceEstimate.region); + const co2e = serviceEstimate.kilowattHours * emissions.mtPerKwHour; + serviceEstimate.co2e = co2e; + }) + }) + + mockData.recommendations.forEach((recommendation) => { + const emissions = mockData.emissions.find(({ region }) => region === recommendation.region); + const co2eSavings = recommendation.kilowattHourSavings * emissions.mtPerKwHour; + recommendation.co2eSavings = co2eSavings; + }) + + fs.writeFileSync( + path.resolve(__dirname, `../packages/client/stub-server/mockData.json`), + JSON.stringify(mockData), + (err) => { + if (err) { + console.error(err) + } + }, + ) +} + +main().catch((error) => { + console.error(error.stack) + process.exit(1) +}); \ No newline at end of file From 8ba0469715f1c0fbab2dbb3eca817773ef920c87 Mon Sep 17 00:00:00 2001 From: Samridhi Agrawal Date: Wed, 29 Mar 2023 13:22:42 +0530 Subject: [PATCH 015/251] add logic to update emission factor --- package.json | 3 +++ scripts/updateMockData.js | 47 +++++++++++++++++++++++++++++++++++---- 2 files changed, 46 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 52507fe06..da8b069f8 100644 --- a/package.json +++ b/package.json @@ -57,5 +57,8 @@ "release": "changeset version && yarn install", "postinstall": "husky install", "version": "yarn changeset version && yarn install --no-immutable" + }, + "dependencies": { + "node-fetch": "2" } } diff --git a/scripts/updateMockData.js b/scripts/updateMockData.js index 12ee7809b..3486e6e7f 100644 --- a/scripts/updateMockData.js +++ b/scripts/updateMockData.js @@ -1,7 +1,16 @@ -const fs = require('fs') -const path = require('path') +const fs = require('fs'); +const path = require('path'); +const fetch = require('node-fetch'); async function main() { + const awsEmissionFactorCsvUrl = 'https://raw.githubusercontent.com/cloud-carbon-footprint/cloud-carbon-coefficients/main/data/grid-emissions-factors-aws.csv'; + const azureEmissionFactorCsvUrl = 'https://raw.githubusercontent.com/cloud-carbon-footprint/cloud-carbon-coefficients/main/data/grid-emissions-factors-azure.csv'; + const gcpEmissionFactorCsvUrl = 'https://raw.githubusercontent.com/cloud-carbon-footprint/cloud-carbon-coefficients/main/data/grid-emissions-factors-gcp.csv'; + + const awsEmission = await fetchEmissionData(awsEmissionFactorCsvUrl); + const azureEmission = await fetchEmissionData(azureEmissionFactorCsvUrl); + const gcpEmission = await fetchEmissionData(gcpEmissionFactorCsvUrl); + const data = fs.readFileSync( path.resolve(__dirname, `../packages/client/stub-server/mockData.json`), 'utf8', @@ -9,18 +18,33 @@ async function main() { const mockData = JSON.parse(data); const today = new Date(); + mockData.emissions.forEach((emission) => { + const awsEmissionFactor = awsEmission.find(({ Region }) => emission.region === Region); + const azureEmissionFactor = azureEmission.find(({ Region }) => emission.region === Region); + const gcpEmissionFactor = gcpEmission.find(({ Region }) => emission.region === Region); + if (awsEmissionFactor !== undefined) { + emission.mtPerKwHour = awsEmissionFactor['CO2e (metric ton/kWh)']; + } + else if (azureEmissionFactor !== undefined) { + emission.mtPerKwHour = azureEmissionFactor['CO2e (metric ton/kWh)']; + } + else if (gcpEmissionFactor !== undefined) { + emission.mtPerKwHour = gcpEmissionFactor['CO2e (metric ton/kWh)']; + } + }) + mockData.footprint.forEach((footprint, index) => { footprint.timestamp = new Date(today.getFullYear(), today.getMonth() - 15 - index, today.getDate()); footprint.serviceEstimates.forEach((serviceEstimate) => { const emissions = mockData.emissions.find(({ region }) => region === serviceEstimate.region); - const co2e = serviceEstimate.kilowattHours * emissions.mtPerKwHour; + const co2e = emissions ? serviceEstimate.kilowattHours * emissions.mtPerKwHour : serviceEstimate.co2e; serviceEstimate.co2e = co2e; }) }) mockData.recommendations.forEach((recommendation) => { const emissions = mockData.emissions.find(({ region }) => region === recommendation.region); - const co2eSavings = recommendation.kilowattHourSavings * emissions.mtPerKwHour; + const co2eSavings = emissions ? recommendation.kilowattHourSavings * emissions.mtPerKwHour : recommendation.co2eSavings; recommendation.co2eSavings = co2eSavings; }) @@ -35,6 +59,21 @@ async function main() { ) } +async function fetchEmissionData(url) { + const response = await fetch(url); + const text = await response.text(); + const csvData = text.split('\n').map(row => row.split(',')); + const headers = csvData[0]; + const jsonData = csvData.slice(1).map(row => { + let obj = {}; + headers.forEach((header, index) => { + obj[header] = row[index]; + }); + return obj; + }); + return jsonData; +} + main().catch((error) => { console.error(error.stack) process.exit(1) From aef236d33284584f31f241f1b05548f73eb7352a Mon Sep 17 00:00:00 2001 From: Samridhi Agrawal Date: Tue, 4 Apr 2023 19:44:39 +0530 Subject: [PATCH 016/251] remove. emission factor update logic and node-fetch package --- package.json | 5 +---- scripts/updateMockData.js | 39 --------------------------------------- 2 files changed, 1 insertion(+), 43 deletions(-) diff --git a/package.json b/package.json index da8b069f8..989cf2b25 100644 --- a/package.json +++ b/package.json @@ -57,8 +57,5 @@ "release": "changeset version && yarn install", "postinstall": "husky install", "version": "yarn changeset version && yarn install --no-immutable" - }, - "dependencies": { - "node-fetch": "2" } -} +} \ No newline at end of file diff --git a/scripts/updateMockData.js b/scripts/updateMockData.js index 3486e6e7f..c61c091e4 100644 --- a/scripts/updateMockData.js +++ b/scripts/updateMockData.js @@ -1,16 +1,7 @@ const fs = require('fs'); const path = require('path'); -const fetch = require('node-fetch'); async function main() { - const awsEmissionFactorCsvUrl = 'https://raw.githubusercontent.com/cloud-carbon-footprint/cloud-carbon-coefficients/main/data/grid-emissions-factors-aws.csv'; - const azureEmissionFactorCsvUrl = 'https://raw.githubusercontent.com/cloud-carbon-footprint/cloud-carbon-coefficients/main/data/grid-emissions-factors-azure.csv'; - const gcpEmissionFactorCsvUrl = 'https://raw.githubusercontent.com/cloud-carbon-footprint/cloud-carbon-coefficients/main/data/grid-emissions-factors-gcp.csv'; - - const awsEmission = await fetchEmissionData(awsEmissionFactorCsvUrl); - const azureEmission = await fetchEmissionData(azureEmissionFactorCsvUrl); - const gcpEmission = await fetchEmissionData(gcpEmissionFactorCsvUrl); - const data = fs.readFileSync( path.resolve(__dirname, `../packages/client/stub-server/mockData.json`), 'utf8', @@ -18,21 +9,6 @@ async function main() { const mockData = JSON.parse(data); const today = new Date(); - mockData.emissions.forEach((emission) => { - const awsEmissionFactor = awsEmission.find(({ Region }) => emission.region === Region); - const azureEmissionFactor = azureEmission.find(({ Region }) => emission.region === Region); - const gcpEmissionFactor = gcpEmission.find(({ Region }) => emission.region === Region); - if (awsEmissionFactor !== undefined) { - emission.mtPerKwHour = awsEmissionFactor['CO2e (metric ton/kWh)']; - } - else if (azureEmissionFactor !== undefined) { - emission.mtPerKwHour = azureEmissionFactor['CO2e (metric ton/kWh)']; - } - else if (gcpEmissionFactor !== undefined) { - emission.mtPerKwHour = gcpEmissionFactor['CO2e (metric ton/kWh)']; - } - }) - mockData.footprint.forEach((footprint, index) => { footprint.timestamp = new Date(today.getFullYear(), today.getMonth() - 15 - index, today.getDate()); footprint.serviceEstimates.forEach((serviceEstimate) => { @@ -59,21 +35,6 @@ async function main() { ) } -async function fetchEmissionData(url) { - const response = await fetch(url); - const text = await response.text(); - const csvData = text.split('\n').map(row => row.split(',')); - const headers = csvData[0]; - const jsonData = csvData.slice(1).map(row => { - let obj = {}; - headers.forEach((header, index) => { - obj[header] = row[index]; - }); - return obj; - }); - return jsonData; -} - main().catch((error) => { console.error(error.stack) process.exit(1) From 03352404dfd5a89fcbbf0b009176474f733a6316 Mon Sep 17 00:00:00 2001 From: Samriddhi Date: Tue, 4 Apr 2023 19:52:30 +0530 Subject: [PATCH 017/251] Update package.json --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 989cf2b25..52507fe06 100644 --- a/package.json +++ b/package.json @@ -58,4 +58,4 @@ "postinstall": "husky install", "version": "yarn changeset version && yarn install --no-immutable" } -} \ No newline at end of file +} From 98b14c274fe4d84c48c325ac4d4a849335334b59 Mon Sep 17 00:00:00 2001 From: Arik Smith Date: Tue, 4 Apr 2023 22:01:01 -0400 Subject: [PATCH 018/251] changeset: modify version bump for google tagging --- .changeset/good-news-chew.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/good-news-chew.md b/.changeset/good-news-chew.md index c096c036f..234f54472 100644 --- a/.changeset/good-news-chew.md +++ b/.changeset/good-news-chew.md @@ -1,5 +1,5 @@ --- -'@cloud-carbon-footprint/gcp': major +'@cloud-carbon-footprint/gcp': minor --- Adds support for including tags and labels in queries via config From 5c2982efb0b72313743827757a896ffca57befff Mon Sep 17 00:00:00 2001 From: Arik Smith Date: Tue, 4 Apr 2023 22:02:10 -0400 Subject: [PATCH 019/251] rearrange gcp tag precedence order --- packages/gcp/src/lib/BillingExportTable.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/gcp/src/lib/BillingExportTable.ts b/packages/gcp/src/lib/BillingExportTable.ts index a62c480c9..845d035be 100644 --- a/packages/gcp/src/lib/BillingExportTable.ts +++ b/packages/gcp/src/lib/BillingExportTable.ts @@ -728,7 +728,7 @@ export default class BillingExportTable { private rawTagsToTagCollection(usageRow: any): TagCollection { const parsedTags: TagCollection = {} - const options = ['tags', 'labels', 'projectLabels'] + const options = ['tags', 'projectLabels', 'labels'] options.forEach((option) => { const tags: string = usageRow[option] From fb436f94ee959c2e5b47138a445f1b9134a7e7e6 Mon Sep 17 00:00:00 2001 From: Arik Smith Date: Wed, 5 Apr 2023 10:51:28 -0400 Subject: [PATCH 020/251] WIP - begin replacing googleapi types with google-cloud package --- packages/gcp/package.json | 1 + .../gcp/src/__tests__/ServiceWrapper.test.ts | 70 +-- .../__tests__/fixtures/googleapis.fixtures.ts | 52 ++- packages/gcp/src/lib/Recommendations.ts | 37 +- packages/gcp/src/lib/ServiceWrapper.ts | 47 +- yarn.lock | 427 +++++++++++++++++- 6 files changed, 532 insertions(+), 102 deletions(-) diff --git a/packages/gcp/package.json b/packages/gcp/package.json index 3d563779d..2a9f3aaa0 100644 --- a/packages/gcp/package.json +++ b/packages/gcp/package.json @@ -44,6 +44,7 @@ "@cloud-carbon-footprint/common": "^1.9.0", "@cloud-carbon-footprint/core": "^0.17.0", "@google-cloud/bigquery": "^5.9.3", + "@google-cloud/compute": "^3.9.0", "@google-cloud/monitoring": "^2.3.5", "@google-cloud/recommender": "^4.2.5", "@google-cloud/resource-manager": "^3.0.0", diff --git a/packages/gcp/src/__tests__/ServiceWrapper.test.ts b/packages/gcp/src/__tests__/ServiceWrapper.test.ts index 17dad7aad..7864be3da 100644 --- a/packages/gcp/src/__tests__/ServiceWrapper.test.ts +++ b/packages/gcp/src/__tests__/ServiceWrapper.test.ts @@ -3,8 +3,7 @@ */ import { ProjectsClient } from '@google-cloud/resource-manager' -import { compute_v1 } from 'googleapis' -import { compute as googleCompute } from 'googleapis/build/src/apis/compute' +import Compute, { protos as googleCompute } from '@google-cloud/compute' import { GoogleAuth } from 'google-auth-library' import { RecommenderClient } from '@google-cloud/recommender' import { GoogleAuthClient, wait } from '@cloud-carbon-footprint/common' @@ -19,17 +18,15 @@ import { mockedInstanceResultItems, mockedMachineTypesGetItems, } from './fixtures/googleapis.fixtures' -import Schema$Instance = compute_v1.Schema$Instance -import Schema$MachineType = compute_v1.Schema$MachineType - +import { mockedProjects } from './fixtures/resourceManager.fixtures' +import { mockStopVMRecommendationsResults } from './fixtures/recommender.fixtures' +import Instance = googleCompute.google.cloud.compute.v1.Instance +import MachineType = googleCompute.google.cloud.compute.v1.MachineType import ServiceWrapper from '../lib/ServiceWrapper' import { ActiveProject, RecommenderRecommendations, } from '../lib/RecommendationsTypes' - -import { mockStopVMRecommendationsResults } from './fixtures/recommender.fixtures' -import { mockedProjects } from './fixtures/resourceManager.fixtures' import { setupSpy, setupSpyWithRejectedValue } from './helpers' jest.mock('@cloud-carbon-footprint/common', () => ({ @@ -67,7 +64,7 @@ describe('GCP Service Wrapper', () => { ;(getClientSpy as jest.Mock).mockResolvedValue(jest.fn()) const googleAuthClient: GoogleAuthClient = await auth.getClient() - const googleComputeClient = googleCompute('v1') + const googleComputeClient = Compute.v1 serviceWrapper = new ServiceWrapper( new ProjectsClient(), @@ -77,29 +74,45 @@ describe('GCP Service Wrapper', () => { ) setupSpy( - googleComputeClient.instances, - 'aggregatedList', + googleComputeClient.InstancesClient.prototype, + 'aggregatedListAsync', mockedInstanceResultItems, ) setupSpy( - googleComputeClient.disks, - 'aggregatedList', + googleComputeClient.DisksClient.prototype, + 'aggregatedListAsync', mockedDisksResultItems, ) - setupSpy(googleComputeClient.disks, 'get', mockedDisksGetSSDDetails) setupSpy( - googleComputeClient.addresses, - 'aggregatedList', + googleComputeClient.DisksClient.prototype, + 'get', + mockedDisksGetSSDDetails, + ) + setupSpy( + googleComputeClient.AddressesClient.prototype, + 'aggregatedListAsync', mockedAddressesResultItems, ) setupSpy( - googleComputeClient.machineTypes, + googleComputeClient.MachineTypesClient.prototype, 'get', mockedMachineTypesGetItems, ) - setupSpy(googleComputeClient.instances, 'get', mockedInstanceGetItems) - setupSpy(googleComputeClient.images, 'get', mockedImageGetDetails) - setupSpy(googleComputeClient.addresses, 'get', mockedAddressGetDetails) + setupSpy( + googleComputeClient.InstancesClient.prototype, + 'get', + mockedInstanceGetItems, + ) + setupSpy( + googleComputeClient.ImagesClient.prototype, + 'get', + mockedImageGetDetails, + ) + setupSpy( + googleComputeClient.AddressesClient.prototype, + 'get', + mockedAddressGetDetails, + ) }) it('gets active projects', async () => { @@ -148,12 +161,11 @@ describe('GCP Service Wrapper', () => { }) it('gets instance details', async () => { - const instanceDetails: Schema$Instance = - await serviceWrapper.getInstanceDetails( - 'project', - 'test-instance', - 'us-west1-b', - ) + const instanceDetails: Instance = await serviceWrapper.getInstanceDetails( + 'project', + 'test-instance', + 'us-west1-b', + ) const expectedResult: InstanceData = { data: { @@ -169,7 +181,7 @@ describe('GCP Service Wrapper', () => { }) it('gets machine type details', async () => { - const machineTypeDetails: Schema$MachineType = + const machineTypeDetails: MachineType = await serviceWrapper.getMachineTypeDetails( 'project', 'test-instance', @@ -241,7 +253,7 @@ describe('GCP Service Wrapper', () => { describe('error handling', () => { let serviceWrapper: ServiceWrapper - const googleComputeClient = googleCompute('v1') + const googleComputeClient = Compute.v1 beforeEach(async () => { const auth = new GoogleAuth({ @@ -264,7 +276,7 @@ describe('GCP Service Wrapper', () => { it('fails to get active zones for project', async () => { setupSpyWithRejectedValue( - googleComputeClient.instances, + googleComputeClient.InstancesClient, 'aggregatedList', 'error', ) diff --git a/packages/gcp/src/__tests__/fixtures/googleapis.fixtures.ts b/packages/gcp/src/__tests__/fixtures/googleapis.fixtures.ts index d5f0abfd2..154ff0d3b 100644 --- a/packages/gcp/src/__tests__/fixtures/googleapis.fixtures.ts +++ b/packages/gcp/src/__tests__/fixtures/googleapis.fixtures.ts @@ -1,49 +1,51 @@ /* * © 2021 Thoughtworks, Inc. */ -import { compute_v1 } from 'googleapis' -import Schema$Instance = compute_v1.Schema$Instance -import Schema$MachineType = compute_v1.Schema$MachineType -import Schema$Image = compute_v1.Schema$Image -import Schema$InstanceAggregatedList = compute_v1.Schema$InstanceAggregatedList -import Schema$Disk = compute_v1.Schema$Disk -import Schema$DiskAggregatedList = compute_v1.Schema$DiskAggregatedList -import Schema$AddressAggregatedList = compute_v1.Schema$AddressAggregatedList -import Schema$Address = compute_v1.Schema$Address + +import { protos as googleCompute } from '@google-cloud/compute' +import Instance = googleCompute.google.cloud.compute.v1.Instance +import MachineType = googleCompute.google.cloud.compute.v1.MachineType +import Disk = googleCompute.google.cloud.compute.v1.Disk +import Image = googleCompute.google.cloud.compute.v1.Image +import Address = googleCompute.google.cloud.compute.v1.Address +import InstanceAggregatedList = google.cloud.compute.v1.InstanceAggregatedList +import DiskAggregatedList = google.cloud.compute.v1.DiskAggregatedList +import AddressAggregatedList = google.cloud.compute.v1.AddressAggregatedList +import { google } from '@google-cloud/compute/build/protos/protos' export type InstanceData = { - data: Schema$Instance + data: Partial } type MachineTypeData = { - data: Schema$MachineType + data: Partial } type ImageDetails = { - data: Schema$Image + data: Partial } type DiskData = { - data: Schema$Disk + data: Partial } type AddressDetails = { - data: Schema$Address + data: Partial
} -type InstanceAggregatedList = { - data: Schema$InstanceAggregatedList +type InstanceAggregatedListData = { + data: Partial } -type DiskAggregatedList = { - data: Schema$DiskAggregatedList +type DiskAggregatedListData = { + data: Partial } -type AddressAggregatedList = { - data: Schema$AddressAggregatedList +type AddressAggregatedListData = { + data: Partial } -export const mockedInstanceResultItems: InstanceAggregatedList = { +export const mockedInstanceResultItems: InstanceAggregatedListData = { data: { items: { 'zones/us-west1-a': { @@ -57,7 +59,7 @@ export const mockedInstanceResultItems: InstanceAggregatedList = { }, } -export const mockedInstanceRegionsResultItems: InstanceAggregatedList = { +export const mockedInstanceRegionsResultItems: InstanceAggregatedListData = { data: { items: { 'regions/us-west1': { @@ -67,7 +69,7 @@ export const mockedInstanceRegionsResultItems: InstanceAggregatedList = { }, } -export const mockedInstanceGlobalResultItems: InstanceAggregatedList = { +export const mockedInstanceGlobalResultItems: InstanceAggregatedListData = { data: { items: { global: { @@ -77,11 +79,11 @@ export const mockedInstanceGlobalResultItems: InstanceAggregatedList = { }, } -export const mockedAddressesResultItems: AddressAggregatedList = { +export const mockedAddressesResultItems: AddressAggregatedListData = { data: { items: {} }, } -export const mockedDisksResultItems: DiskAggregatedList = { +export const mockedDisksResultItems: DiskAggregatedListData = { data: { items: {} }, } diff --git a/packages/gcp/src/lib/Recommendations.ts b/packages/gcp/src/lib/Recommendations.ts index 260802b32..548fdcd55 100644 --- a/packages/gcp/src/lib/Recommendations.ts +++ b/packages/gcp/src/lib/Recommendations.ts @@ -3,12 +3,12 @@ */ import R from 'ramda' -import { compute_v1 } from 'googleapis' -import { google } from '@google-cloud/recommender/build/protos/protos' -import IRecommendation = google.cloud.recommender.v1.IRecommendation -import IImpact = google.cloud.recommender.v1.IImpact -import Schema$Instance = compute_v1.Schema$Instance -import Schema$Disk = compute_v1.Schema$Disk +import { google as googleRecommender } from '@google-cloud/recommender/build/protos/protos' +import { protos as googleCompute } from '@google-cloud/compute' +import IRecommendation = googleRecommender.cloud.recommender.v1.IRecommendation +import IImpact = googleRecommender.cloud.recommender.v1.IImpact +import Instance = googleCompute.google.cloud.compute.v1.Instance +import Disk = googleCompute.google.cloud.compute.v1.Disk import { COMPUTE_PROCESSOR_TYPES, ComputeEstimator, @@ -16,6 +16,7 @@ import { ICloudRecommendationsService, StorageEstimator, StorageUsage, + KilowattHourTotals, } from '@cloud-carbon-footprint/core' import { convertBytesToGigabytes, @@ -36,7 +37,6 @@ import { } from './RecommendationsTypes' import ServiceWrapper from './ServiceWrapper' import { GCP_REGIONS } from './GCPRegions' -import { KilowattHourTotals } from '@cloud-carbon-footprint/core' export default class Recommendations implements ICloudRecommendationsService { readonly RECOMMENDER_IDS: string[] = [ @@ -213,7 +213,7 @@ export default class Recommendations implements ICloudRecommendationsService { .split('/') .pop(), } - let diskDetails: Schema$Disk + let diskDetails: Disk try { switch (recommendation.recommenderSubtype) { case RECOMMENDATION_TYPES.STOP_VM: @@ -253,7 +253,7 @@ export default class Recommendations implements ICloudRecommendationsService { timestamp: undefined, } resourceDetails = { - resourceId: instanceDetails.id, + resourceId: instanceDetails.id.toString(), resourceName: instanceDetails.name, } return [footprintEstimate, resourceDetails] @@ -291,7 +291,7 @@ export default class Recommendations implements ICloudRecommendationsService { zone, ) resourceDetails = { - resourceId: currentMachineInstanceDetails.id, + resourceId: currentMachineInstanceDetails.id.toString(), resourceName: currentMachineInstanceDetails.name, } return [footprintEstimate, resourceDetails] @@ -309,7 +309,7 @@ export default class Recommendations implements ICloudRecommendationsService { ) resourceDetails = { - resourceId: diskDetails.id, + resourceId: diskDetails.id.toString(), resourceName: diskDetails.name, } return [footprintEstimate, resourceDetails] @@ -320,14 +320,14 @@ export default class Recommendations implements ICloudRecommendationsService { imageId, ) const imageArchiveSizeGigabytes = convertBytesToGigabytes( - parseFloat(imageDetails.archiveSizeBytes), + parseFloat(imageDetails.archiveSizeBytes.toString()), ) footprintEstimate = this.estimateStorageCO2eSavings( imageArchiveSizeGigabytes, this.parseRegionFromZone(zone), ) resourceDetails = { - resourceId: imageDetails.id, + resourceId: imageDetails.id.toString(), resourceName: imageDetails.name, } return [footprintEstimate, resourceDetails] @@ -340,7 +340,7 @@ export default class Recommendations implements ICloudRecommendationsService { zone, ) resourceDetails = { - resourceId: addressDetails.id, + resourceId: addressDetails.id.toString(), resourceName: addressDetails.name, } @@ -383,15 +383,12 @@ export default class Recommendations implements ICloudRecommendationsService { ) } - private async getCO2EstimatesSavingsForDisk( - diskDetails: Schema$Disk, - zone: string, - ) { + private async getCO2EstimatesSavingsForDisk(diskDetails: Disk, zone: string) { const storageType = this.googleServiceWrapper.getStorageTypeFromDiskName( diskDetails.type.split('/').pop(), ) return this.estimateStorageCO2eSavings( - parseFloat(diskDetails.sizeGb), + parseFloat(diskDetails.sizeGb.toString()), this.parseRegionFromZone(zone), storageType, ) @@ -399,7 +396,7 @@ export default class Recommendations implements ICloudRecommendationsService { private async getCO2EstimatedSavingsForInstance( projectId: string, - instanceDetails: Schema$Instance, + instanceDetails: Instance, zone: string, ) { const machineType = instanceDetails.machineType.split('/').pop() diff --git a/packages/gcp/src/lib/ServiceWrapper.ts b/packages/gcp/src/lib/ServiceWrapper.ts index 21a0e60df..c65d3b0b4 100644 --- a/packages/gcp/src/lib/ServiceWrapper.ts +++ b/packages/gcp/src/lib/ServiceWrapper.ts @@ -2,35 +2,33 @@ * © 2021 Thoughtworks, Inc. */ import R from 'ramda' -import { ProjectsClient, protos } from '@google-cloud/resource-manager' -import { APIEndpoint } from 'googleapis-common' +import { + ProjectsClient, + protos as googleResource, +} from '@google-cloud/resource-manager' import { RecommenderClient } from '@google-cloud/recommender' -import { compute_v1 } from 'googleapis' -import Project = protos.google.cloud.resourcemanager.v3.IProject -import Schema$Instance = compute_v1.Schema$Instance -import Schema$MachineType = compute_v1.Schema$MachineType -import Schema$Disk = compute_v1.Schema$Disk -import Schema$Image = compute_v1.Schema$Image -import Schema$Address = compute_v1.Schema$Address -import Schema$InstancesScopedList = compute_v1.Schema$InstancesScopedList -import Schema$DisksScopedList = compute_v1.Schema$DisksScopedList -import Schema$AddressesScopedList = compute_v1.Schema$AddressesScopedList +import Compute, { protos as googleCompute } from '@google-cloud/compute' +import Project = googleResource.google.cloud.resourcemanager.v3.IProject import { GoogleAuthClient, Logger, wait } from '@cloud-carbon-footprint/common' import { InstanceData } from '../__tests__/fixtures/googleapis.fixtures' import { ActiveProject, RecommenderRecommendations, } from './RecommendationsTypes' +import Instance = googleCompute.google.cloud.compute.v1.Instance +import MachineType = googleCompute.google.cloud.compute.v1.MachineType +import Disk = googleCompute.google.cloud.compute.v1.Disk +import Image = googleCompute.google.cloud.compute.v1.Image +import Address = googleCompute.google.cloud.compute.v1.Address +import InstancesScopedList = googleCompute.google.cloud.compute.v1.InstancesScopedList +import DisksScopedList = googleCompute.google.cloud.compute.v1.DisksScopedList +import AddressesScopedList = googleCompute.google.cloud.compute.v1.AddressesScopedList const RETRY_AFTER = 10 type Zone = [ string, - ( - | Schema$InstancesScopedList - | Schema$DisksScopedList - | Schema$AddressesScopedList - ), + InstancesScopedList | DisksScopedList | AddressesScopedList, ] export default class ServiceWrapper { @@ -39,7 +37,7 @@ export default class ServiceWrapper { constructor( private readonly googleProjectsClient: ProjectsClient, private readonly googleAuthClient: GoogleAuthClient, - private readonly googleComputeClient: APIEndpoint, + private readonly googleComputeClient: typeof Compute, private readonly googleRecommenderClient: RecommenderClient, ) { this.serviceWrapperLogger = new Logger('GCP Service Wrapper') @@ -161,7 +159,7 @@ export default class ServiceWrapper { projectId: string, instanceId: string, zone: string, - ): Promise { + ): Promise { const computeEngineRequest = { project: projectId, zone: zone, @@ -178,7 +176,7 @@ export default class ServiceWrapper { projectId: string, machineType: string, zone: string, - ): Promise { + ): Promise { const machineTypeRequest = { project: projectId, zone: zone, @@ -199,7 +197,7 @@ export default class ServiceWrapper { projectId: string, diskId: string, zone: string, - ): Promise { + ): Promise { const diskDetailsRequest = { project: projectId, zone: zone, @@ -210,10 +208,7 @@ export default class ServiceWrapper { return result.data } - async getImageDetails( - projectId: string, - imageId: string, - ): Promise { + async getImageDetails(projectId: string, imageId: string): Promise { const ImageDetailsRequest = { project: projectId, image: imageId, @@ -229,7 +224,7 @@ export default class ServiceWrapper { projectId: string, addressId: string, zone: string, - ): Promise { + ): Promise
{ const AddressDetailsRequest = { project: projectId, region: zone, diff --git a/yarn.lock b/yarn.lock index 1cceba6f9..431237869 100644 --- a/yarn.lock +++ b/yarn.lock @@ -692,6 +692,15 @@ __metadata: languageName: node linkType: hard +"@babel/parser@npm:^7.20.15": + version: 7.21.4 + resolution: "@babel/parser@npm:7.21.4" + bin: + parser: ./bin/babel-parser.js + checksum: de610ecd1bff331766d0c058023ca11a4f242bfafefc42caf926becccfb6756637d167c001987ca830dd4b34b93c629a4cef63f8c8c864a8564cdfde1989ac77 + languageName: node + linkType: hard + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@npm:^7.18.6": version: 7.18.6 resolution: "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@npm:7.18.6" @@ -2503,6 +2512,7 @@ __metadata: "@cloud-carbon-footprint/common": ^1.9.0 "@cloud-carbon-footprint/core": ^0.17.0 "@google-cloud/bigquery": ^5.9.3 + "@google-cloud/compute": ^3.9.0 "@google-cloud/monitoring": ^2.3.5 "@google-cloud/recommender": ^4.2.5 "@google-cloud/resource-manager": ^3.0.0 @@ -3032,6 +3042,15 @@ __metadata: languageName: node linkType: hard +"@google-cloud/compute@npm:^3.9.0": + version: 3.9.0 + resolution: "@google-cloud/compute@npm:3.9.0" + dependencies: + google-gax: ^3.5.2 + checksum: 3607c400a1e7cd4fcd5219ed7b11e15654be83a4d7138f4bc1139eff1d6cc61986aa4dd9bd4920f673104f9a0ba25e9085373a9953a1a225dca61e69152f9c8d + languageName: node + linkType: hard + "@google-cloud/iam-credentials@npm:^1.1.1": version: 1.2.0 resolution: "@google-cloud/iam-credentials@npm:1.2.0" @@ -3194,6 +3213,21 @@ __metadata: languageName: node linkType: hard +"@grpc/proto-loader@npm:^0.7.0": + version: 0.7.6 + resolution: "@grpc/proto-loader@npm:0.7.6" + dependencies: + "@types/long": ^4.0.1 + lodash.camelcase: ^4.3.0 + long: ^4.0.0 + protobufjs: ^7.0.0 + yargs: ^16.2.0 + bin: + proto-loader-gen-types: build/bin/proto-loader-gen-types.js + checksum: cc42649cf65c74f627ac80b1f3ed275c4cf96dbc27728cc887e91e217c69a3bd6b94dfa7571725a94538d84735af53d35e9583cc77eb65f3c035106216cc4a1b + languageName: node + linkType: hard + "@hapi/hoek@npm:^9.0.0": version: 9.3.0 resolution: "@hapi/hoek@npm:9.3.0" @@ -3567,6 +3601,15 @@ __metadata: languageName: node linkType: hard +"@jsdoc/salty@npm:^0.2.1": + version: 0.2.5 + resolution: "@jsdoc/salty@npm:0.2.5" + dependencies: + lodash: ^4.17.21 + checksum: 16c65d48c340d8f1b892797bdd6ace4f90d916d16bed5023f2a5421240ead20e828031dfb1d07b8eb0e172a62f532c3c005287e723e30ee9a0c8a0d7d2e98953 + languageName: node + linkType: hard + "@leichtgewicht/ip-codec@npm:^2.0.1": version: 2.0.4 resolution: "@leichtgewicht/ip-codec@npm:2.0.4" @@ -5758,6 +5801,16 @@ __metadata: languageName: node linkType: hard +"@types/glob@npm:*": + version: 8.1.0 + resolution: "@types/glob@npm:8.1.0" + dependencies: + "@types/minimatch": ^5.1.2 + "@types/node": "*" + checksum: 9101f3a9061e40137190f70626aa0e202369b5ec4012c3fabe6f5d229cce04772db9a94fa5a0eb39655e2e4ad105c38afbb4af56a56c0996a8c7d4fc72350e3d + languageName: node + linkType: hard + "@types/glob@npm:^7.1.1": version: 7.2.0 resolution: "@types/glob@npm:7.2.0" @@ -5915,6 +5968,13 @@ __metadata: languageName: node linkType: hard +"@types/linkify-it@npm:*": + version: 3.0.2 + resolution: "@types/linkify-it@npm:3.0.2" + checksum: dff8f10fafb885422474e456596f12d518ec4cdd6c33cca7a08e7c86b912d301ed91cf5a7613e148c45a12600dc9ab3d85ad16d5b48dc1aaeda151a68f16b536 + languageName: node + linkType: hard + "@types/lodash@npm:^4.14.72": version: 4.14.182 resolution: "@types/lodash@npm:4.14.182" @@ -5929,6 +5989,23 @@ __metadata: languageName: node linkType: hard +"@types/markdown-it@npm:^12.2.3": + version: 12.2.3 + resolution: "@types/markdown-it@npm:12.2.3" + dependencies: + "@types/linkify-it": "*" + "@types/mdurl": "*" + checksum: 868824a3e4d00718ba9cd4762cf16694762a670860f4b402e6e9f952b6841a2027488bdc55d05c2b960bf5078df21a9d041270af7e8949514645fe88fdb722ac + languageName: node + linkType: hard + +"@types/mdurl@npm:*": + version: 1.0.2 + resolution: "@types/mdurl@npm:1.0.2" + checksum: 79c7e523b377f53cf1f5a240fe23d0c6cae856667692bd21bf1d064eafe5ccc40ae39a2aa0a9a51e8c94d1307228c8f6b121e847124591a9a828c3baf65e86e2 + languageName: node + linkType: hard + "@types/mime@npm:^1": version: 1.3.2 resolution: "@types/mime@npm:1.3.2" @@ -5943,6 +6020,13 @@ __metadata: languageName: node linkType: hard +"@types/minimatch@npm:^5.1.2": + version: 5.1.2 + resolution: "@types/minimatch@npm:5.1.2" + checksum: 0391a282860c7cb6fe262c12b99564732401bdaa5e395bee9ca323c312c1a0f45efbf34dce974682036e857db59a5c9b1da522f3d6055aeead7097264c8705a8 + languageName: node + linkType: hard + "@types/minimist@npm:^1.2.0": version: 1.2.2 resolution: "@types/minimist@npm:1.2.2" @@ -6156,6 +6240,16 @@ __metadata: languageName: node linkType: hard +"@types/rimraf@npm:^3.0.2": + version: 3.0.2 + resolution: "@types/rimraf@npm:3.0.2" + dependencies: + "@types/glob": "*" + "@types/node": "*" + checksum: b47fa302f46434cba704d20465861ad250df79467d3d289f9d6490d3aeeb41e8cb32dd80bd1a8fd833d1e185ac719fbf9be12e05ad9ce9be094d8ee8f1405347 + languageName: node + linkType: hard + "@types/scheduler@npm:*": version: 0.16.2 resolution: "@types/scheduler@npm:0.16.2" @@ -6759,6 +6853,15 @@ __metadata: languageName: node linkType: hard +"acorn@npm:^8.8.0": + version: 8.8.2 + resolution: "acorn@npm:8.8.2" + bin: + acorn: bin/acorn + checksum: f790b99a1bf63ef160c967e23c46feea7787e531292bb827126334612c234ed489a0dc2c7ba33156416f0ffa8d25bf2b0fdb7f35c2ba60eb3e960572bece4001 + languageName: node + linkType: hard + "adal-node@npm:^0.2.2": version: 0.2.3 resolution: "adal-node@npm:0.2.3" @@ -7835,7 +7938,7 @@ __metadata: languageName: node linkType: hard -"bluebird@npm:^3.5.0, bluebird@npm:^3.5.1, bluebird@npm:^3.5.5": +"bluebird@npm:^3.5.0, bluebird@npm:^3.5.1, bluebird@npm:^3.5.5, bluebird@npm:^3.7.2": version: 3.7.2 resolution: "bluebird@npm:3.7.2" checksum: 869417503c722e7dc54ca46715f70e15f4d9c602a423a02c825570862d12935be59ed9c7ba34a9b31f186c017c23cac6b54e35446f8353059c101da73eac22ef @@ -8269,6 +8372,15 @@ __metadata: languageName: node linkType: hard +"catharsis@npm:^0.9.0": + version: 0.9.0 + resolution: "catharsis@npm:0.9.0" + dependencies: + lodash: ^4.17.15 + checksum: da867df1fd01823ea5a7283886ba382f6eb5b1fe5af356e00fd944a02d9b867f4ea2fc7f61416c53427f62760fdbd41614f6e8ae37686d2c3a4696871526df20 + languageName: node + linkType: hard + "chai@npm:4.3.4": version: 4.3.4 resolution: "chai@npm:4.3.4" @@ -10535,6 +10647,13 @@ __metadata: languageName: node linkType: hard +"entities@npm:~2.1.0": + version: 2.1.0 + resolution: "entities@npm:2.1.0" + checksum: a10a877e489586a3f6a691fe49bf3fc4e58f06c8e80522f08214a5150ba457e7017b447d4913a3fa041bda06ee4c92517baa4d8d75373eaa79369e9639225ffd + languageName: node + linkType: hard + "env-paths@npm:^2.2.0": version: 2.2.1 resolution: "env-paths@npm:2.2.1" @@ -10719,6 +10838,25 @@ __metadata: languageName: node linkType: hard +"escodegen@npm:^1.13.0": + version: 1.14.3 + resolution: "escodegen@npm:1.14.3" + dependencies: + esprima: ^4.0.1 + estraverse: ^4.2.0 + esutils: ^2.0.2 + optionator: ^0.8.1 + source-map: ~0.6.1 + dependenciesMeta: + source-map: + optional: true + bin: + escodegen: bin/escodegen.js + esgenerate: bin/esgenerate.js + checksum: 381cdc4767ecdb221206bbbab021b467bbc2a6f5c9a99c9e6353040080bdd3dfe73d7604ad89a47aca6ea7d58bc635f6bd3fbc8da9a1998e9ddfa8372362ccd0 + languageName: node + linkType: hard + "escodegen@npm:^2.0.0": version: 2.0.0 resolution: "escodegen@npm:2.0.0" @@ -11003,6 +11141,13 @@ __metadata: languageName: node linkType: hard +"eslint-visitor-keys@npm:^3.4.0": + version: 3.4.0 + resolution: "eslint-visitor-keys@npm:3.4.0" + checksum: 33159169462d3989321a1ec1e9aaaf6a24cc403d5d347e9886d1b5bfe18ffa1be73bdc6203143a28a606b142b1af49787f33cff0d6d0813eb5f2e8d2e1a6043c + languageName: node + linkType: hard + "eslint-webpack-plugin@npm:^3.1.1": version: 3.2.0 resolution: "eslint-webpack-plugin@npm:3.2.0" @@ -11082,6 +11227,17 @@ __metadata: languageName: node linkType: hard +"espree@npm:^9.0.0": + version: 9.5.1 + resolution: "espree@npm:9.5.1" + dependencies: + acorn: ^8.8.0 + acorn-jsx: ^5.3.2 + eslint-visitor-keys: ^3.4.0 + checksum: cdf6e43540433d917c4f2ee087c6e987b2063baa85a1d9cdaf51533d78275ebd5910c42154e7baf8e3e89804b386da0a2f7fad2264d8f04420e7506bf87b3b88 + languageName: node + linkType: hard + "espree@npm:^9.3.2": version: 9.3.2 resolution: "espree@npm:9.3.2" @@ -11121,7 +11277,7 @@ __metadata: languageName: node linkType: hard -"estraverse@npm:^4.1.1": +"estraverse@npm:^4.1.1, estraverse@npm:^4.2.0": version: 4.3.0 resolution: "estraverse@npm:4.3.0" checksum: a6299491f9940bb246124a8d44b7b7a413a8336f5436f9837aaa9330209bd9ee8af7e91a654a3545aee9c54b3308e78ee360cef1d777d37cfef77d2fa33b5827 @@ -12177,6 +12333,19 @@ __metadata: languageName: node linkType: hard +"glob@npm:^8.0.0": + version: 8.1.0 + resolution: "glob@npm:8.1.0" + dependencies: + fs.realpath: ^1.0.0 + inflight: ^1.0.4 + inherits: 2 + minimatch: ^5.0.1 + once: ^1.3.0 + checksum: 92fbea3221a7d12075f26f0227abac435de868dd0736a17170663783296d0dd8d3d532a5672b4488a439bf5d7fb85cdd07c11185d6cd39184f0385cbdfb86a47 + languageName: node + linkType: hard + "glob@npm:^8.0.1": version: 8.0.3 resolution: "glob@npm:8.0.3" @@ -12328,6 +12497,32 @@ __metadata: languageName: node linkType: hard +"google-gax@npm:^3.5.2": + version: 3.6.0 + resolution: "google-gax@npm:3.6.0" + dependencies: + "@grpc/grpc-js": ~1.8.0 + "@grpc/proto-loader": ^0.7.0 + "@types/long": ^4.0.0 + "@types/rimraf": ^3.0.2 + abort-controller: ^3.0.0 + duplexify: ^4.0.0 + fast-text-encoding: ^1.0.3 + google-auth-library: ^8.0.2 + is-stream-ended: ^0.1.4 + node-fetch: ^2.6.1 + object-hash: ^3.0.0 + proto3-json-serializer: ^1.0.0 + protobufjs: 7.2.3 + protobufjs-cli: 1.1.1 + retry-request: ^5.0.0 + bin: + compileProtos: build/tools/compileProtos.js + minifyProtoJson: build/tools/minify.js + checksum: 4c7b1b5a278ffda3eaa92e7f25a9e0ea2d0a76aca704aca4baee4fe9d109916e97e83062ad751006ed93ac675a7fd74028061204fa9ed92833a853e6657a71a1 + languageName: node + linkType: hard + "google-p12-pem@npm:^3.1.3": version: 3.1.4 resolution: "google-p12-pem@npm:3.1.4" @@ -12389,6 +12584,13 @@ __metadata: languageName: node linkType: hard +"graceful-fs@npm:^4.1.9": + version: 4.2.11 + resolution: "graceful-fs@npm:4.2.11" + checksum: ac85f94da92d8eb6b7f5a8b20ce65e43d66761c55ce85ac96df6865308390da45a8d3f0296dd3a663de65d30ba497bd46c696cc1e248c72b13d6d567138a4fc7 + languageName: node + linkType: hard + "grapheme-splitter@npm:^1.0.4": version: 1.0.4 resolution: "grapheme-splitter@npm:1.0.4" @@ -14549,6 +14751,15 @@ __metadata: languageName: node linkType: hard +"js2xmlparser@npm:^4.0.2": + version: 4.0.2 + resolution: "js2xmlparser@npm:4.0.2" + dependencies: + xmlcreate: ^2.0.4 + checksum: 55e3af71dc0104941dfc3e85452230db42ff3870a5777d1ea26bc0c68743f49113a517a7b305421a932b29f10058a012a7da8f5ba07860a05a1dce9fe5b62962 + languageName: node + linkType: hard + "jsbn@npm:~0.1.0": version: 0.1.1 resolution: "jsbn@npm:0.1.1" @@ -14556,6 +14767,31 @@ __metadata: languageName: node linkType: hard +"jsdoc@npm:^4.0.0": + version: 4.0.2 + resolution: "jsdoc@npm:4.0.2" + dependencies: + "@babel/parser": ^7.20.15 + "@jsdoc/salty": ^0.2.1 + "@types/markdown-it": ^12.2.3 + bluebird: ^3.7.2 + catharsis: ^0.9.0 + escape-string-regexp: ^2.0.0 + js2xmlparser: ^4.0.2 + klaw: ^3.0.0 + markdown-it: ^12.3.2 + markdown-it-anchor: ^8.4.1 + marked: ^4.0.10 + mkdirp: ^1.0.4 + requizzle: ^0.2.3 + strip-json-comments: ^3.1.0 + underscore: ~1.13.2 + bin: + jsdoc: jsdoc.js + checksum: 04bf5ab005349b7581bd0e72ed99933eb71a41dcb47235b486b7d9146fbdf212a53e0cc044abe48ccf46012bd812dc1dfc007c6d679660ebdd053cd000242515 + languageName: node + linkType: hard + "jsdom@npm:^16.6.0": version: 16.7.0 resolution: "jsdom@npm:16.7.0" @@ -14977,6 +15213,15 @@ __metadata: languageName: node linkType: hard +"klaw@npm:^3.0.0": + version: 3.0.0 + resolution: "klaw@npm:3.0.0" + dependencies: + graceful-fs: ^4.1.9 + checksum: 1bf9de22392c80d28de8a2babd6f0de29fa52fcdc1654838fd35174b3641c168ec32b8b03022191e3c190efd535c31fce23f85e29cb260245571da7263ef418e + languageName: node + linkType: hard + "kleur@npm:^3.0.3": version: 3.0.3 resolution: "kleur@npm:3.0.3" @@ -15124,6 +15369,15 @@ __metadata: languageName: node linkType: hard +"linkify-it@npm:^3.0.1": + version: 3.0.3 + resolution: "linkify-it@npm:3.0.3" + dependencies: + uc.micro: ^1.0.1 + checksum: 31367a4bb70c5bbc9703246236b504b0a8e049bcd4e0de4291fa50f0ebdebf235b5eb54db6493cb0b1319357c6eeafc4324c9f4aa34b0b943d9f2e11a1268fbc + languageName: node + linkType: hard + "lint-staged@npm:^12.1.7": version: 12.5.0 resolution: "lint-staged@npm:12.5.0" @@ -15490,6 +15744,13 @@ __metadata: languageName: node linkType: hard +"long@npm:^5.0.0": + version: 5.2.1 + resolution: "long@npm:5.2.1" + checksum: 9264da12d1b7df67e5aa6da4498144293caf1ad12e7f092efe4e9a2d32c53f0bbf7334f7cef997080a2a3af061142558ab366efa71698d98b1cdb883477445a7 + languageName: node + linkType: hard + "loose-envify@npm:^1.1.0, loose-envify@npm:^1.4.0": version: 1.4.0 resolution: "loose-envify@npm:1.4.0" @@ -15708,6 +15969,40 @@ __metadata: languageName: node linkType: hard +"markdown-it-anchor@npm:^8.4.1": + version: 8.6.7 + resolution: "markdown-it-anchor@npm:8.6.7" + peerDependencies: + "@types/markdown-it": "*" + markdown-it: "*" + checksum: 828236768ac7f61ed5591393c1b1bfc5dbf2b6d0c58a3deec606c61dddaa12658a34450cbef37ab50a04453e618ce1efd47d86e4e52595024334898fd306225b + languageName: node + linkType: hard + +"markdown-it@npm:^12.3.2": + version: 12.3.2 + resolution: "markdown-it@npm:12.3.2" + dependencies: + argparse: ^2.0.1 + entities: ~2.1.0 + linkify-it: ^3.0.1 + mdurl: ^1.0.1 + uc.micro: ^1.0.5 + bin: + markdown-it: bin/markdown-it.js + checksum: 890555711c1c00fa03b936ca2b213001a3b9b37dea140d8445ae4130ce16628392aad24b12e2a0a9935336ca5951f2957a38f4e5309a2e38eab44e25ff32a41e + languageName: node + linkType: hard + +"marked@npm:^4.0.10": + version: 4.3.0 + resolution: "marked@npm:4.3.0" + bin: + marked: bin/marked.js + checksum: 0db6817893952c3ec710eb9ceafb8468bf5ae38cb0f92b7b083baa13d70b19774674be04db5b817681fa7c5c6a088f61300815e4dd75a59696f4716ad69f6260 + languageName: node + linkType: hard + "match-url-wildcard@npm:0.0.4": version: 0.0.4 resolution: "match-url-wildcard@npm:0.0.4" @@ -15731,6 +16026,13 @@ __metadata: languageName: node linkType: hard +"mdurl@npm:^1.0.1": + version: 1.0.1 + resolution: "mdurl@npm:1.0.1" + checksum: 71731ecba943926bfbf9f9b51e28b5945f9411c4eda80894221b47cc105afa43ba2da820732b436f0798fd3edbbffcd1fc1415843c41a87fea08a41cc1e3d02b + languageName: node + linkType: hard + "media-typer@npm:0.3.0": version: 0.3.0 resolution: "media-typer@npm:0.3.0" @@ -18645,6 +18947,38 @@ __metadata: languageName: node linkType: hard +"proto3-json-serializer@npm:^1.0.0": + version: 1.1.0 + resolution: "proto3-json-serializer@npm:1.1.0" + dependencies: + protobufjs: ^7.0.0 + checksum: a68f7102746a21e2fe6d7afdf89f47b94084a1165ca3062e284922ecca12f20a3ec911bc5f3fdb63fc0cfc9a0cff2b4396befb988c66061ae686d17ff0b6a9fd + languageName: node + linkType: hard + +"protobufjs-cli@npm:1.1.1": + version: 1.1.1 + resolution: "protobufjs-cli@npm:1.1.1" + dependencies: + chalk: ^4.0.0 + escodegen: ^1.13.0 + espree: ^9.0.0 + estraverse: ^5.1.0 + glob: ^8.0.0 + jsdoc: ^4.0.0 + minimist: ^1.2.0 + semver: ^7.1.2 + tmp: ^0.2.1 + uglify-js: ^3.7.7 + peerDependencies: + protobufjs: ^7.0.0 + bin: + pbjs: bin/pbjs + pbts: bin/pbts + checksum: 124a2cb10d6fccdd6e8f2984b0f7d9a351d9c1efd17f237acd4a9e7c4b82d63265364b1c86bfa5c6a6fa17d7119182c4c323a8972c0078e1ac5c5f653d096f9b + languageName: node + linkType: hard + "protobufjs@npm:6.11.3, protobufjs@npm:^6.11.2, protobufjs@npm:^6.11.3": version: 6.11.3 resolution: "protobufjs@npm:6.11.3" @@ -18669,6 +19003,26 @@ __metadata: languageName: node linkType: hard +"protobufjs@npm:7.2.3, protobufjs@npm:^7.0.0": + version: 7.2.3 + resolution: "protobufjs@npm:7.2.3" + dependencies: + "@protobufjs/aspromise": ^1.1.2 + "@protobufjs/base64": ^1.1.2 + "@protobufjs/codegen": ^2.0.4 + "@protobufjs/eventemitter": ^1.1.0 + "@protobufjs/fetch": ^1.1.0 + "@protobufjs/float": ^1.0.2 + "@protobufjs/inquire": ^1.1.0 + "@protobufjs/path": ^1.1.2 + "@protobufjs/pool": ^1.1.0 + "@protobufjs/utf8": ^1.1.0 + "@types/node": ">=13.7.0" + long: ^5.0.0 + checksum: 9afa6de5fced0139a5180c063718508fac3ea734a9f1aceb99712367b15473a83327f91193f16b63540f9112b09a40912f5f0441a9b0d3f3c6a1c7f707d78249 + languageName: node + linkType: hard + "protocols@npm:^1.4.0": version: 1.4.8 resolution: "protocols@npm:1.4.8" @@ -19725,6 +20079,15 @@ __metadata: languageName: node linkType: hard +"requizzle@npm:^0.2.3": + version: 0.2.4 + resolution: "requizzle@npm:0.2.4" + dependencies: + lodash: ^4.17.21 + checksum: fceaa448b235f9ed111aa58360129225a3cec1a897a23293dc08d2a00f001756c042a62df0a9d4d1e2669ace52dec960aea73437f407b30c51bfba2e9da208b7 + languageName: node + linkType: hard + "reselect@npm:^4.0.0, reselect@npm:^4.1.5": version: 4.1.6 resolution: "reselect@npm:4.1.6" @@ -19897,6 +20260,16 @@ __metadata: languageName: node linkType: hard +"retry-request@npm:^5.0.0": + version: 5.0.2 + resolution: "retry-request@npm:5.0.2" + dependencies: + debug: ^4.1.1 + extend: ^3.0.2 + checksum: d6c95d27f4468aa5557605d811cfaa5862be0eaff9fc5f18a338a7c17a7972fbec5b6142abb6b1e494b4c02df875fec2f1c3a281bf79900d33607d8536277ffe + languageName: node + linkType: hard + "retry@npm:0.13.1, retry@npm:^0.13.1": version: 0.13.1 resolution: "retry@npm:0.13.1" @@ -20267,6 +20640,17 @@ __metadata: languageName: node linkType: hard +"semver@npm:^7.1.2": + version: 7.3.8 + resolution: "semver@npm:7.3.8" + dependencies: + lru-cache: ^6.0.0 + bin: + semver: bin/semver.js + checksum: ba9c7cbbf2b7884696523450a61fee1a09930d888b7a8d7579025ad93d459b2d1949ee5bbfeb188b2be5f4ac163544c5e98491ad6152df34154feebc2cc337c1 + languageName: node + linkType: hard + "send@npm:0.18.0": version: 0.18.0 resolution: "send@npm:0.18.0" @@ -22095,6 +22479,15 @@ __metadata: languageName: node linkType: hard +"tmp@npm:^0.2.1": + version: 0.2.1 + resolution: "tmp@npm:0.2.1" + dependencies: + rimraf: ^3.0.0 + checksum: 8b1214654182575124498c87ca986ac53dc76ff36e8f0e0b67139a8d221eaecfdec108c0e6ec54d76f49f1f72ab9325500b246f562b926f85bcdfca8bf35df9e + languageName: node + linkType: hard + "tmpl@npm:1.0.5": version: 1.0.5 resolution: "tmpl@npm:1.0.5" @@ -22574,6 +22967,13 @@ __metadata: languageName: node linkType: hard +"uc.micro@npm:^1.0.1, uc.micro@npm:^1.0.5": + version: 1.0.6 + resolution: "uc.micro@npm:1.0.6" + checksum: 6898bb556319a38e9cf175e3628689347bd26fec15fc6b29fa38e0045af63075ff3fea4cf1fdba9db46c9f0cbf07f2348cd8844889dd31ebd288c29fe0d27e7a + languageName: node + linkType: hard + "uglify-js@npm:^3.1.4": version: 3.16.2 resolution: "uglify-js@npm:3.16.2" @@ -22583,6 +22983,15 @@ __metadata: languageName: node linkType: hard +"uglify-js@npm:^3.7.7": + version: 3.17.4 + resolution: "uglify-js@npm:3.17.4" + bin: + uglifyjs: bin/uglifyjs + checksum: 7b3897df38b6fc7d7d9f4dcd658599d81aa2b1fb0d074829dd4e5290f7318dbca1f4af2f45acb833b95b1fe0ed4698662ab61b87e94328eb4c0a0d3435baf924 + languageName: node + linkType: hard + "uid-number@npm:0.0.6": version: 0.0.6 resolution: "uid-number@npm:0.0.6" @@ -22616,6 +23025,13 @@ __metadata: languageName: node linkType: hard +"underscore@npm:~1.13.2": + version: 1.13.6 + resolution: "underscore@npm:1.13.6" + checksum: d5cedd14a9d0d91dd38c1ce6169e4455bb931f0aaf354108e47bd46d3f2da7464d49b2171a5cf786d61963204a42d01ea1332a903b7342ad428deaafaf70ec36 + languageName: node + linkType: hard + "unicode-canonical-property-names-ecmascript@npm:^2.0.0": version: 2.0.0 resolution: "unicode-canonical-property-names-ecmascript@npm:2.0.0" @@ -23790,6 +24206,13 @@ __metadata: languageName: node linkType: hard +"xmlcreate@npm:^2.0.4": + version: 2.0.4 + resolution: "xmlcreate@npm:2.0.4" + checksum: b8dd52668b9aea77cd1408fa85538c14bb8dcc98b4e7bb51e76696c9c115d59eba7240298d0c4fd2caf8f1a8e283ab4e5c7b9a6bcfcf23a8b48f5068b677b748 + languageName: node + linkType: hard + "xpath.js@npm:~1.1.0": version: 1.1.0 resolution: "xpath.js@npm:1.1.0" From 9340ca33a096a3fa18259dd4bd3c61c6260c9a25 Mon Sep 17 00:00:00 2001 From: Cam Casher Date: Wed, 5 Apr 2023 15:30:10 -0600 Subject: [PATCH 021/251] [#1104] fixes azure recs --- .../src/__tests__/AdvisorRecommendations.test.ts | 8 ++++---- packages/azure/src/lib/AdvisorRecommendations.ts | 11 +++++++---- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/packages/azure/src/__tests__/AdvisorRecommendations.test.ts b/packages/azure/src/__tests__/AdvisorRecommendations.test.ts index 07cf4d128..9058f34e6 100644 --- a/packages/azure/src/__tests__/AdvisorRecommendations.test.ts +++ b/packages/azure/src/__tests__/AdvisorRecommendations.test.ts @@ -53,7 +53,7 @@ describe('Azure Advisor Recommendations Service', () => { costSavings: 30, instanceName: 'test-vm-name', kilowattHourSavings: 3.4082495999999995, - recommendationDetail: 'Shutdown instance: test-vm-name.', + recommendationDetail: 'Shutdown: test-vm-name.', recommendationType: 'Shutdown', region: 'EastUS', resourceId: 'test-resource-id', @@ -80,7 +80,7 @@ describe('Azure Advisor Recommendations Service', () => { costSavings: 30, instanceName: 'test-vm-name', kilowattHourSavings: 157.19829814079998, - recommendationDetail: 'Shutdown instance: test-vm-name.', + recommendationDetail: 'Shutdown: test-vm-name.', recommendationType: 'Shutdown', region: 'EastUS', resourceId: 'test-resource-id', @@ -93,7 +93,7 @@ describe('Azure Advisor Recommendations Service', () => { costSavings: 30, instanceName: 'test-vm-name', kilowattHourSavings: 1.7041247999999998, - recommendationDetail: 'Shutdown instance: test-vm-name.', + recommendationDetail: 'Shutdown: test-vm-name.', recommendationType: 'Shutdown', region: 'EastUS', resourceId: 'test-resource-id', @@ -126,7 +126,7 @@ describe('Azure Advisor Recommendations Service', () => { instanceName: 'test-vm-name', kilowattHourSavings: 49.55996979795109, recommendationDetail: - 'Right-size instance: test-vm-name. Update instance type M16ms to M8ms', + 'Right-size: test-vm-name. Update instance type M16ms to M8ms', recommendationType: 'Right-size', region: 'EastUS', resourceId: 'test-resource-id', diff --git a/packages/azure/src/lib/AdvisorRecommendations.ts b/packages/azure/src/lib/AdvisorRecommendations.ts index 8c043ca18..9622d342a 100644 --- a/packages/azure/src/lib/AdvisorRecommendations.ts +++ b/packages/azure/src/lib/AdvisorRecommendations.ts @@ -81,9 +81,11 @@ export default class AdvisorRecommendations co2eSavings += currentMemoryFootprint.co2e } + const resizingTypes = ['Right-size', 'SkuChange'] if ( - recommendation.extendedProperties.recommendationType === - 'Right-size' + resizingTypes.includes( + recommendation.extendedProperties.recommendationType, + ) ) { const rightsizingTargetRecommendation = new RightsizingTargetRecommendation(recommendation) @@ -134,13 +136,14 @@ export default class AdvisorRecommendations rightsizingTargetRecommendation?: RightsizingTargetRecommendation, ): string { const modifyDetail = `Update instance type ${rightsizingCurrentRecommendation.instanceType} to ${rightsizingTargetRecommendation?.instanceType}` - let defaultDetail = `${rightsizingCurrentRecommendation.type} instance: ${rightsizingCurrentRecommendation.instanceName}.` + let defaultDetail = `${rightsizingCurrentRecommendation.type}: ${rightsizingCurrentRecommendation.instanceName}.` if (!rightsizingCurrentRecommendation.instanceName) { defaultDetail = `${rightsizingCurrentRecommendation.type} instance with Resource ID: ${rightsizingCurrentRecommendation.resourceId}.` } const recommendationTypes: { [key: string]: string } = { Shutdown: defaultDetail, - 'Right-size': `${defaultDetail} ${modifyDetail}`, + ['Right-size']: `${defaultDetail} ${modifyDetail}`, + SkuChange: `${defaultDetail} ${modifyDetail}`, } return recommendationTypes[rightsizingCurrentRecommendation.type] } From b98fd6d12c38b81f6404751747a09702adcffadf Mon Sep 17 00:00:00 2001 From: Cam Casher Date: Wed, 5 Apr 2023 15:30:53 -0600 Subject: [PATCH 022/251] changeset: azure minor bump --- .changeset/shaggy-melons-exercise.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/shaggy-melons-exercise.md diff --git a/.changeset/shaggy-melons-exercise.md b/.changeset/shaggy-melons-exercise.md new file mode 100644 index 000000000..2a4000695 --- /dev/null +++ b/.changeset/shaggy-melons-exercise.md @@ -0,0 +1,5 @@ +--- +'@cloud-carbon-footprint/azure': minor +--- + +updates azure recs to support skuchange From f7dcb39ec3635b2fb500925bbc366c1893029982 Mon Sep 17 00:00:00 2001 From: Arik Smith Date: Wed, 5 Apr 2023 18:18:55 -0400 Subject: [PATCH 023/251] add documentation for gcp tags --- .../docs/ConfigurationOptions/Tagging.md | 45 ++++++++++++++++++- 1 file changed, 43 insertions(+), 2 deletions(-) diff --git a/microsite/docs/ConfigurationOptions/Tagging.md b/microsite/docs/ConfigurationOptions/Tagging.md index 0da2a28fb..4e44778ad 100644 --- a/microsite/docs/ConfigurationOptions/Tagging.md +++ b/microsite/docs/ConfigurationOptions/Tagging.md @@ -32,7 +32,10 @@ Example: Specifying `AWS_RESOURCE_TAG_NAMES=["user:Environment", “aws:createdB } ``` -### AWS Tagging Conventions +## Tagging Conventions + +### AWS + The AWS Cost and Usage Reporting (CUR) translates tag names to names that are valid Athena column names. On top of this, it also adds a prefix to distinguish between user-created tags and AWS-internal tags. While not documented, we have found that a tag such as SourceRepository will be `user:SourceRepository` in CUR, and `resource_tags_user_source_repository` in Athena (AWS-internal tags will be prefixed with `aws:` instead of `user:` in CUR). To include a list of resource tags for AWS, you must set the `AWS_RESOURCE_TAG_NAMES` with the appropriate prefixes included. @@ -41,7 +44,45 @@ Example: For more information on AWS tags, you can refer to the [official AWS tagging documentation](https://docs.aws.amazon.com/general/latest/gr/aws_tagging.html). -### Azure Tagging Conventions +### GCP + +The exported billing data for Google Cloud includes both tags and labels for listed resources. We support both properties as configurable tagging options. This includes organization-level tags, in addition to both project and resource-level labels. +To include a list of resource tags/labels for GCP, you must set the GCP_RESOURCE_TAG_NAMES. Each tag type will need to be specified using the same variable, but using a colon-separated prefix similar to AWS to specify the label/tag type that the key refers to. + +Example: `GCP_RESOURCE_TAG_NAMES=["label:example-label", "project:example-projectLabel", "tag:example-tag" ]` will respectively represent the desired label, project label, and tag keys to include. + +_Note_: Regardless of the type of tag specified, the resulting key/value pairs for a resource will be aggregated all under the `tags` property for each result. Therefore, the example above, will yield the following results: + +```JSON +{ + "cloudProvider": "GCP", + ...FootprintEstimate, + "tags": { + "example-label": "value", + "example-tag": "value", + "example-projectLabel": "value" + } +} +``` + +With this in mind, it is recommended to use unique keys when including multiple types in order to avoid potential conflicts. If a duplication occurs, the lowest-level type will take precedence (labels > project labels > tags). + +#### Permissions + +Including tags and labels in query results requires the following roles assigned to a service account: + +```text +roles/resourcemanager.tagUser +roles/bigquery.dataOwner +roles/bigquery.user +``` + +If the service account you're using does not have the correct permissions to view these properties in BigQuery, then you may experience your requests hanging after adding tags to the config variable. + +For more information on GCP tags and labels, you can refer to the [official GCP tagging documentation](https://cloud.google.com/resource-manager/docs/tags/tags-creating-and-managing). + +### Azure + The [Azure Consumption API](https://learn.microsoft.com/en-us/rest/api/consumption/) exposes a resource's `tag` property as a field in each row of billing data. This field allows for access to the `resourceGroup` property as well. Both of these properties can be accessed as tagging configs within CCF. To include a list of resource tags for Azure, you must set the `Azure_Resource_Tag_Names`. From e8fe39228e0e05e8a58ab7a10c7eda4691231c6a Mon Sep 17 00:00:00 2001 From: Ashutosh Krishna Date: Thu, 6 Apr 2023 08:34:00 +0530 Subject: [PATCH 024/251] [#1106] Update emissions factors in mockData.json file to match cloud constants/coefficients --- packages/client/stub-server/mockData.json | 134 +++++++++++----------- 1 file changed, 65 insertions(+), 69 deletions(-) diff --git a/packages/client/stub-server/mockData.json b/packages/client/stub-server/mockData.json index 9722e63bb..04ee6f0bc 100644 --- a/packages/client/stub-server/mockData.json +++ b/packages/client/stub-server/mockData.json @@ -2,251 +2,247 @@ "emissions": [ { "region": "us-east-1", - "mtPerKwHour": 0.0004545 + "mtPerKwHour": 0.000379069 }, { "region": "us-east-2", - "mtPerKwHour": 0.000475105 + "mtPerKwHour": 0.000410608 }, { "region": "us-west-1", - "mtPerKwHour": 0.000351533 + "mtPerKwHour": 0.000322167 }, { "region": "us-west-2", - "mtPerKwHour": 0.000351533 + "mtPerKwHour": 0.000322167 }, { "region": "us-gov-east-1", - "mtPerKwHour": 0.0004545 + "mtPerKwHour": 0.000379069 }, { "region": "us-gov-west-1", - "mtPerKwHour": 0.000351533 + "mtPerKwHour": 0.000322167 }, { "region": "af-south-1", - "mtPerKwHour": 0.000928 + "mtPerKwHour": 0.0009006 }, { "region": "ap-east-1", - "mtPerKwHour": 0.00081 + "mtPerKwHour": 0.00071 }, { "region": "ap-south-1", - "mtPerKwHour": 0.000708 + "mtPerKwHour": 0.0007082 }, { "region": "ap-northeast-3", - "mtPerKwHour": 0.000506 + "mtPerKwHour": 0.0004658 }, { "region": "ap-northeast-2", - "mtPerKwHour": 0.0005 + "mtPerKwHour": 0.0004156 }, { "region": "ap-southeast-1", - "mtPerKwHour": 0.0004085 + "mtPerKwHour": 0.000408 }, { "region": "ap-southeast-2", - "mtPerKwHour": 0.00079 + "mtPerKwHour": 0.00076 }, { "region": "ap-northeast-1", - "mtPerKwHour": 0.000506 + "mtPerKwHour": 0.0004658 }, { "region": "ca-central-1", - "mtPerKwHour": 0.00013 + "mtPerKwHour": 0.00012 }, { "region": "cn-north-1", - "mtPerKwHour": 0.000555 + "mtPerKwHour": 0.0005374 }, { "region": "cn-northwest-1", - "mtPerKwHour": 0.000555 + "mtPerKwHour": 0.0005374 }, { "region": "eu-central-1", - "mtPerKwHour": 0.000338 + "mtPerKwHour": 0.000311 }, { "region": "eu-west-1", - "mtPerKwHour": 0.000316 + "mtPerKwHour": 0.0002786 }, { "region": "eu-west-2", - "mtPerKwHour": 0.000228 + "mtPerKwHour": 0.000225 }, { "region": "eu-south-1", - "mtPerKwHour": 0.000233 + "mtPerKwHour": 0.0002134 }, { "region": "eu-west-3", - "mtPerKwHour": 0.000052 + "mtPerKwHour": 0.0000511 }, { "region": "eu-north-1", - "mtPerKwHour": 0.000008 + "mtPerKwHour": 0.0000088 }, { "region": "me-south-1", - "mtPerKwHour": 0.000732 + "mtPerKwHour": 0.0005059 }, { "region": "sa-east-1", - "mtPerKwHour": 0.000074 + "mtPerKwHour": 0.0000617 }, { "region": "us-central1", - "mtPerKwHour": 0.000540461 + "mtPerKwHour": 0.000454 }, { "region": "us-central2", - "mtPerKwHour": 0.000540461 + "mtPerKwHour": 0.000454 }, { "region": "us-east1", - "mtPerKwHour": 0.0004545 + "mtPerKwHour": 0.00048 }, { "region": "us-east4", - "mtPerKwHour": 0.0004545 + "mtPerKwHour": 0.000361 }, { "region": "us-west1", - "mtPerKwHour": 0.000228876 + "mtPerKwHour": 0.000078 }, { "region": "us-west2", - "mtPerKwHour": 0.000351533 + "mtPerKwHour": 0.000253 }, { "region": "us-west3", - "mtPerKwHour": 0.000351533 + "mtPerKwHour": 0.000533 }, { "region": "us-west4", - "mtPerKwHour": 0.000351533 + "mtPerKwHour": 0.000455 }, { "region": "asia-east1", - "mtPerKwHour": 0.000509 + "mtPerKwHour": 0.00054 }, { "region": "asia-east2", - "mtPerKwHour": 0.00081 + "mtPerKwHour": 0.000453 }, { "region": "asia-northeast1", - "mtPerKwHour": 0.000506 + "mtPerKwHour": 0.000554 }, { "region": "asia-northeast2", - "mtPerKwHour": 0.000506 + "mtPerKwHour": 0.000442 }, { "region": "asia-northeast3", - "mtPerKwHour": 0.0005 + "mtPerKwHour": 0.000457 }, { "region": "asia-south1", - "mtPerKwHour": 0.000708 + "mtPerKwHour": 0.000721 }, { "region": "asia-southeast1", - "mtPerKwHour": 0.0004085 + "mtPerKwHour": 0.000493 }, { "region": "asia-southeast2", - "mtPerKwHour": 0.000761 + "mtPerKwHour": 0.000647 }, { "region": "australia-southeast1", - "mtPerKwHour": 0.00079 + "mtPerKwHour": 0.000727 }, { "region": "europe-north1", - "mtPerKwHour": 0.000086 + "mtPerKwHour": 0.000133 }, { "region": "europe-west1", - "mtPerKwHour": 0.000167 + "mtPerKwHour": 0.000212 }, { "region": "europe-west2", - "mtPerKwHour": 0.000228 + "mtPerKwHour": 0.000231 }, { "region": "europe-west3", - "mtPerKwHour": 0.000338 + "mtPerKwHour": 0.000293 }, { "region": "europe-west4", - "mtPerKwHour": 0.00039 + "mtPerKwHour": 0.00041 }, { "region": "europe-west6", - "mtPerKwHour": 0.00001182 + "mtPerKwHour": 0.000087 }, { "region": "northamerica-northeast1", - "mtPerKwHour": 0.00013 + "mtPerKwHour": 0.000027 }, { "region": "southamerica-east1", - "mtPerKwHour": 0.000074 - }, - { - "region": "unknown", - "mtPerKwHour": 0.0004108907 + "mtPerKwHour": 0.000103 }, { "region": "AP East", - "mtPerKwHour": 0.00081 + "mtPerKwHour": 0.00071 }, { "region": "EU West", - "mtPerKwHour": 0.00039 + "mtPerKwHour": 0.0003284 }, { "region": "IN Central", - "mtPerKwHour": 0.000708 + "mtPerKwHour": 0.0007082 }, { "region": "UK South", - "mtPerKwHour": 0.0004545 + "mtPerKwHour": 0.000225 }, { "region": "UK West", - "mtPerKwHour": 0.000228 + "mtPerKwHour": 0.000225 }, { "region": "US Central", - "mtPerKwHour": 0.000540461 + "mtPerKwHour": 0.000426254 }, { "region": "US East", - "mtPerKwHour": 0.0004545 + "mtPerKwHour": 0.000379069 }, { "region": "US South Central", - "mtPerKwHour": 0.000424877 + "mtPerKwHour": 0.000373231 }, { "region": "US West", - "mtPerKwHour": 0.000351533 + "mtPerKwHour": 0.000322167 }, { "region": "US West 2", - "mtPerKwHour": 0.000351533 + "mtPerKwHour": 0.000322167 }, { "region": "Unknown", - "mtPerKwHour": 0.0004074 + "mtPerKwHour": 0.0003852304903666667 } ], "footprint": [ @@ -1542,9 +1538,9 @@ "instanceName": "test-instance-9", "recommendationDetail": "Save cost by performing a STOP_VM for instance: test-instance-9.", "resourceId": 8928403120086348000, - "kilowattHourSavings": 0.0, - "costSavings": 0.0, - "co2eSavings": 0.0 + "kilowattHourSavings": 0, + "costSavings": 0, + "co2eSavings": 0 }, { "cloudProvider": "GCP", @@ -1573,4 +1569,4 @@ "co2eSavings": 0.002 } ] -} +} \ No newline at end of file From c4c7e56407674a40b14347b75258a6dcdcc54765 Mon Sep 17 00:00:00 2001 From: Arik Smith Date: Thu, 6 Apr 2023 16:08:56 -0400 Subject: [PATCH 025/251] [#1106] add SkuChange type to test fixture --- .../__tests__/AdvisorRecommendations.test.ts | 16 ++++++++- .../__tests__/fixtures/advisor.fixtures.ts | 34 +++++++++++++++++++ 2 files changed, 49 insertions(+), 1 deletion(-) diff --git a/packages/azure/src/__tests__/AdvisorRecommendations.test.ts b/packages/azure/src/__tests__/AdvisorRecommendations.test.ts index 9058f34e6..64d365750 100644 --- a/packages/azure/src/__tests__/AdvisorRecommendations.test.ts +++ b/packages/azure/src/__tests__/AdvisorRecommendations.test.ts @@ -103,7 +103,7 @@ describe('Azure Advisor Recommendations Service', () => { expect(result).toEqual(expectedResult) }) - it('Get recommendations from Advisor API type: Right-size', async () => { + it('Get rightsizing recommendations from Advisor API', async () => { mockListRecommendations.list.mockReturnValue( mockRightsizeVmRecommendationsResults, ) @@ -131,6 +131,20 @@ describe('Azure Advisor Recommendations Service', () => { region: 'EastUS', resourceId: 'test-resource-id', }, + { + accountId: subscriptionId, + accountName: subscriptionId, + cloudProvider: 'AZURE', + co2eSavings: 0.018786648191339524, + costSavings: 30, + instanceName: 'test-vm-name', + kilowattHourSavings: 49.55996979795109, + recommendationDetail: + 'SkuChange: test-vm-name. Update instance type M16ms to M8ms', + recommendationType: 'SkuChange', + region: 'EastUS', + resourceId: 'test-resource-id', + }, ] expect(result).toEqual(expectedResult) diff --git a/packages/azure/src/__tests__/fixtures/advisor.fixtures.ts b/packages/azure/src/__tests__/fixtures/advisor.fixtures.ts index ab1f01bd4..eea84c22e 100644 --- a/packages/azure/src/__tests__/fixtures/advisor.fixtures.ts +++ b/packages/azure/src/__tests__/fixtures/advisor.fixtures.ts @@ -176,4 +176,38 @@ export const mockRightsizeVmRecommendationsResults: ResourceRecommendationBase[] resourceId: 'test-resource-id', }, }, + { + id: 'test-recommendation-id', + name: 'test-recommendation-id', + type: 'Microsoft.Advisor/recommendations', + category: 'Cost', + impact: 'High', + impactedField: 'Microsoft.Compute/virtualMachines', + impactedValue: 'test-vm-name', + lastUpdated: new Date('2022-11-14T18:34:02.954Z'), + recommendationTypeId: 'test-recommendation-type-id', + shortDescription: { + problem: 'Right-size or shutdown underutilized virtual machines', + solution: 'Right-size or shutdown underutilized virtual machines', + }, + extendedProperties: { + MaxCpuP95: '1', + MaxTotalNetworkP95: '0', + MaxMemoryP95: '59', + savingsCurrency: 'USD', + savingsAmount: '30', + annualSavingsAmount: '360', + deploymentId: 'test-deployment-id', + roleName: 'test-vm-name', + currentSku: 'M16ms/Standard', + targetSku: 'M8ms Spot', + recommendationMessage: 'Change the sku of this Virtual Machine', + recommendationType: 'SkuChange', + regionId: 'useast', + subscriptionId: 'test-subscription-id', + }, + resourceMetadata: { + resourceId: 'test-resource-id', + }, + }, ] From f1b1700b631740835cc7ebd4b824e838aeb60cf8 Mon Sep 17 00:00:00 2001 From: Cam Casher Date: Thu, 13 Apr 2023 13:13:55 -0600 Subject: [PATCH 026/251] [#1111] adds DCU to gcp compute types to calculate dataplex --- packages/gcp/src/lib/BillingExportTypes.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/gcp/src/lib/BillingExportTypes.ts b/packages/gcp/src/lib/BillingExportTypes.ts index 7737c63b0..3ec096592 100644 --- a/packages/gcp/src/lib/BillingExportTypes.ts +++ b/packages/gcp/src/lib/BillingExportTypes.ts @@ -95,6 +95,7 @@ export const COMPUTE_STRING_FORMATS: string[] = [ 'CPUs', 'Kubernetes Clusters', 'GPU', + 'DCU', ] export const NETWORKING_STRING_FORMATS: string[] = [ From 56273914e43ff2afa28fbdb4ba331e43db73b443 Mon Sep 17 00:00:00 2001 From: Cam Casher Date: Thu, 13 Apr 2023 13:14:53 -0600 Subject: [PATCH 027/251] patches gcp version --- .changeset/moody-emus-wash.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/moody-emus-wash.md diff --git a/.changeset/moody-emus-wash.md b/.changeset/moody-emus-wash.md new file mode 100644 index 000000000..8ce4b02e5 --- /dev/null +++ b/.changeset/moody-emus-wash.md @@ -0,0 +1,5 @@ +--- +'@cloud-carbon-footprint/gcp': patch +--- + +adds DCU to gcp compute types From 07ee2ddfe7b5efbef2b3816b02c249c04f35ffa9 Mon Sep 17 00:00:00 2001 From: Cam Casher Date: Fri, 14 Apr 2023 13:58:32 -0600 Subject: [PATCH 028/251] [#1112] bumps ramda deps --- packages/app/package.json | 2 +- packages/aws/package.json | 2 +- packages/cli/package.json | 4 +- packages/client/package.json | 4 +- packages/core/package.json | 4 +- .../default-app/packages/cli/package.json.hbs | 4 +- .../packages/client/package.json.hbs | 4 +- yarn.lock | 51 +++++++++++-------- 8 files changed, 42 insertions(+), 33 deletions(-) diff --git a/packages/app/package.json b/packages/app/package.json index a5f7ea864..934cca6b0 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -50,7 +50,7 @@ "@sovpro/delimited-stream": "^1.1.0", "moment": "^2.29.1", "mongodb": "^4.7.0", - "ramda": "^0.28.0" + "ramda": "^0.29.0" }, "devDependencies": { "@types/jest": "^27.4.0", diff --git a/packages/aws/package.json b/packages/aws/package.json index 5bb11632f..22962b560 100644 --- a/packages/aws/package.json +++ b/packages/aws/package.json @@ -48,7 +48,7 @@ "aws-sdk-mock": "^5.1.0", "csvtojson": "^2.0.10", "moment": "^2.29.1", - "ramda": "^0.28.0" + "ramda": "^0.29.0" }, "devDependencies": { "@types/jest": "^27.4.0", diff --git a/packages/cli/package.json b/packages/cli/package.json index ca28c29b2..f836a1b52 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -74,7 +74,7 @@ "@cloud-carbon-footprint/common": "^1.10.0", "@types/cli-table": "^0.3.0", "@types/prompts": "^2.0.12", - "@types/ramda": "^0.27.40", + "@types/ramda": "^0.29.0", "aws-sdk": "^2.910.0", "cli-table": "^0.3.6", "commander": "^10.0.0", @@ -83,7 +83,7 @@ "dotenv": "^16.0.0", "fs-extra": "^10.0.0", "moment": "^2.29.1", - "ramda": "^0.28.0", + "ramda": "^0.29.0", "typed-prompts": "^1.5.0" }, "lint-staged": { diff --git a/packages/client/package.json b/packages/client/package.json index 5b1fe406f..e51be2bb8 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -37,7 +37,7 @@ "express": "^4.17.1", "helmet": "^5.0.1", "moment": "^2.29.1", - "ramda": "^0.28.0", + "ramda": "^0.29.0", "react": "^18.2.0", "react-apexcharts": "^1.3.9", "react-dates": "^21.8.0", @@ -96,7 +96,7 @@ "@types/jest": "^27.4.0", "@types/mockdate": "^3.0.0", "@types/node": "^17.0.8", - "@types/ramda": "^0.27.40", + "@types/ramda": "^0.29.0", "@types/react": "^18.0.27", "@types/react-dates": "^21.8.2", "@types/react-dom": "^17.0.11", diff --git a/packages/core/package.json b/packages/core/package.json index 9783060eb..105a714b5 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -65,10 +65,10 @@ }, "dependencies": { "@cloud-carbon-footprint/common": "^1.10.0", - "@types/ramda": "^0.27.40", + "@types/ramda": "^0.29.0", "aws-sdk": "^2.910.0", "moment": "^2.29.1", - "ramda": "^0.28.0" + "ramda": "^0.29.0" }, "lint-staged": { "*.{js,ts}": [ diff --git a/packages/create-app/templates/default-app/packages/cli/package.json.hbs b/packages/create-app/templates/default-app/packages/cli/package.json.hbs index 2e8a63f1a..dfa26850f 100644 --- a/packages/create-app/templates/default-app/packages/cli/package.json.hbs +++ b/packages/create-app/templates/default-app/packages/cli/package.json.hbs @@ -74,7 +74,7 @@ "@cloud-carbon-footprint/common": "^{{version '@cloud-carbon-footprint/common'}}", "@types/cli-table": "^0.3.0", "@types/prompts": "^2.0.12", - "@types/ramda": "^0.27.40", + "@types/ramda": "^0.29.0", "aws-sdk": "^2.910.0", "cli-table": "^0.3.6", "commander": "^8.0.0", @@ -83,7 +83,7 @@ "dotenv": "^16.0.0", "fs-extra": "^10.0.0", "moment": "^2.29.1", - "ramda": "^0.28.0", + "ramda": "^0.29.0", "typed-prompts": "^1.5.0" }, "lint-staged": { diff --git a/packages/create-app/templates/default-app/packages/client/package.json.hbs b/packages/create-app/templates/default-app/packages/client/package.json.hbs index 89b91b165..5767c1499 100644 --- a/packages/create-app/templates/default-app/packages/client/package.json.hbs +++ b/packages/create-app/templates/default-app/packages/client/package.json.hbs @@ -32,7 +32,7 @@ "express": "^4.17.1", "helmet": "^5.0.1", "moment": "^2.29.1", - "ramda": "^0.28.0", + "ramda": "^0.29.0", "react": "^18.2.0", "react-apexcharts": "^1.3.9", "react-dates": "^21.8.0", @@ -91,7 +91,7 @@ "@types/jest": "^27.4.0", "@types/mockdate": "^3.0.0", "@types/node": "^17.0.8", - "@types/ramda": "^0.27.40", + "@types/ramda": "^0.29.0", "@types/react": "^18.0.27", "@types/react-dates": "^21.8.2", "@types/react-dom": "^17.0.11", diff --git a/yarn.lock b/yarn.lock index 1cceba6f9..ae90c089d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2214,7 +2214,7 @@ __metadata: moment: ^2.29.1 mongodb: ^4.7.0 prettier: ^2.5.1 - ramda: ^0.28.0 + ramda: ^0.29.0 rimraf: ^3.0.2 ts-jest: ^27.1.2 ts-node: ^10.4.0 @@ -2249,7 +2249,7 @@ __metadata: moment: ^2.29.1 onchange: ^7.1.0 prettier: ^2.5.1 - ramda: ^0.28.0 + ramda: ^0.29.0 rimraf: ^3.0.2 source-map-support: ^0.5.19 ts-jest: ^27.1.2 @@ -2310,7 +2310,7 @@ __metadata: "@types/jest-when": ^3.5.0 "@types/node": ^17.0.8 "@types/prompts": ^2.0.12 - "@types/ramda": ^0.27.40 + "@types/ramda": ^0.29.0 "@types/source-map-support": ^0.5.3 "@typescript-eslint/eslint-plugin": ^5.9.0 "@typescript-eslint/parser": ^5.9.0 @@ -2332,7 +2332,7 @@ __metadata: moment: ^2.29.1 onchange: ^7.1.0 prettier: ^2.5.1 - ramda: ^0.28.0 + ramda: ^0.29.0 rimraf: ^3.0.2 source-map-support: ^0.5.19 ts-jest: ^27.1.2 @@ -2365,7 +2365,7 @@ __metadata: "@types/jest": ^27.4.0 "@types/mockdate": ^3.0.0 "@types/node": ^17.0.8 - "@types/ramda": ^0.27.40 + "@types/ramda": ^0.29.0 "@types/react": ^18.0.27 "@types/react-dates": ^21.8.2 "@types/react-dom": ^17.0.11 @@ -2390,7 +2390,7 @@ __metadata: mockdate: ^3.0.5 moment: ^2.29.1 prettier: ^2.5.1 - ramda: ^0.28.0 + ramda: ^0.29.0 react: ^18.2.0 react-apexcharts: ^1.3.9 react-dates: ^21.8.0 @@ -2441,7 +2441,7 @@ __metadata: "@types/jest": ^27.4.0 "@types/jest-when": ^3.5.0 "@types/node": ^17.0.8 - "@types/ramda": ^0.27.40 + "@types/ramda": ^0.29.0 "@types/source-map-support": ^0.5.3 "@typescript-eslint/eslint-plugin": ^5.9.0 "@typescript-eslint/parser": ^5.9.0 @@ -2456,7 +2456,7 @@ __metadata: moment: ^2.29.1 onchange: ^7.1.0 prettier: ^2.5.1 - ramda: ^0.28.0 + ramda: ^0.29.0 rimraf: ^3.0.2 source-map-support: ^0.5.19 ts-jest: ^27.1.2 @@ -6029,12 +6029,12 @@ __metadata: languageName: node linkType: hard -"@types/ramda@npm:^0.27.40": - version: 0.27.66 - resolution: "@types/ramda@npm:0.27.66" +"@types/ramda@npm:^0.29.0": + version: 0.29.0 + resolution: "@types/ramda@npm:0.29.0" dependencies: - ts-toolbelt: ^6.15.1 - checksum: eea577e4a0934849b4103c1452a7c8ddbc9bbf0e2aafb908467212654555145f846a16fe737563b582e8fb5bd6698481ebec1237537e5e662587c47f626e4c92 + types-ramda: ^0.29.1 + checksum: f133675d7cf0df801de3d9790bc04af76e489179c31f21f6bbd8939dd659cefd878cce77f7abba41b04c6986c25008e3dd55c3250315e5232b5bccac213e5da9 languageName: node linkType: hard @@ -18848,10 +18848,10 @@ __metadata: languageName: node linkType: hard -"ramda@npm:^0.28.0": - version: 0.28.0 - resolution: "ramda@npm:0.28.0" - checksum: 44ea6e5010bba70151b6a92d8114a91915e8b5a16105cce65fae58c9d7386b812c429645e35f21141d7087568550ce383bc10ee1a65cdec951f4b69ea457e6a4 +"ramda@npm:^0.29.0": + version: 0.29.0 + resolution: "ramda@npm:0.29.0" + checksum: 9ab26c06eb7545cbb7eebcf75526d6ee2fcaae19e338f165b2bf32772121e7b28192d6664d1ba222ff76188ba26ab307342d66e805dbb02c860560adc4d5dd57 languageName: node linkType: hard @@ -22342,10 +22342,10 @@ __metadata: languageName: node linkType: hard -"ts-toolbelt@npm:^6.15.1": - version: 6.15.5 - resolution: "ts-toolbelt@npm:6.15.5" - checksum: 24ad00cfd9ce735c76c873a9b1347eac475b94e39ebbdf100c9019dce88dd5f4babed52884cf82bb456a38c28edd0099ab6f704b84b2e5e034852b618472c1f3 +"ts-toolbelt@npm:^9.6.0": + version: 9.6.0 + resolution: "ts-toolbelt@npm:9.6.0" + checksum: 9f35fd95d895a5d32ea9fd2e532a695b0bae6cbff6832b77292efa188a0ed1ed6e54f63f74a8920390f3d909a7a3adb20a144686372a8e78b420246a9bd3d58a languageName: node linkType: hard @@ -22554,6 +22554,15 @@ __metadata: languageName: node linkType: hard +"types-ramda@npm:^0.29.1": + version: 0.29.1 + resolution: "types-ramda@npm:0.29.1" + dependencies: + ts-toolbelt: ^9.6.0 + checksum: b67391954cbd75528a09ff8433c11c32ae3f3af28e2b0c0fb05807208cdb6d221aa860732d28174adcd3038ec2ad6c3d9c26bbc1b5379dca555de6e9504668e4 + languageName: node + linkType: hard + "typescript@npm:^4.6.2": version: 4.7.4 resolution: "typescript@npm:4.7.4" From 653863309d38f7efc8c819ee12d256f964d5626f Mon Sep 17 00:00:00 2001 From: Cam Casher Date: Fri, 14 Apr 2023 14:04:10 -0600 Subject: [PATCH 029/251] changeset: patch bumps for package dep updates --- .changeset/proud-humans-work.md | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 .changeset/proud-humans-work.md diff --git a/.changeset/proud-humans-work.md b/.changeset/proud-humans-work.md new file mode 100644 index 000000000..695ef29e6 --- /dev/null +++ b/.changeset/proud-humans-work.md @@ -0,0 +1,10 @@ +--- +'@cloud-carbon-footprint/app': patch +'@cloud-carbon-footprint/aws': patch +'@cloud-carbon-footprint/cli': patch +'@cloud-carbon-footprint/client': patch +'@cloud-carbon-footprint/core': patch +'@cloud-carbon-footprint/create-app': patch +--- + +bumps ramda dependency From d99bb9dcd8d33a594a81b99df29ad14a8fae7af1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 28 Mar 2023 21:40:19 +0000 Subject: [PATCH 030/251] Bump webpack from 5.73.0 to 5.76.3 Bumps [webpack](https://github.com/webpack/webpack) from 5.73.0 to 5.76.3. - [Release notes](https://github.com/webpack/webpack/releases) - [Commits](https://github.com/webpack/webpack/compare/v5.73.0...v5.76.3) --- updated-dependencies: - dependency-name: webpack dependency-type: indirect ... Signed-off-by: dependabot[bot] --- yarn.lock | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/yarn.lock b/yarn.lock index ae90c089d..cfe7b4625 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10502,13 +10502,13 @@ __metadata: languageName: node linkType: hard -"enhanced-resolve@npm:^5.9.3": - version: 5.10.0 - resolution: "enhanced-resolve@npm:5.10.0" +"enhanced-resolve@npm:^5.10.0": + version: 5.12.0 + resolution: "enhanced-resolve@npm:5.12.0" dependencies: graceful-fs: ^4.2.4 tapable: ^2.2.0 - checksum: 0bb9830704db271610f900e8d79d70a740ea16f251263362b0c91af545576d09fe50103496606c1300a05e588372d6f9780a9bc2e30ce8ef9b827ec8f44687ff + checksum: bf3f787facaf4ce3439bef59d148646344e372bef5557f0d37ea8aa02c51f50a925cd1f07b8d338f18992c29f544ec235a8c64bcdb56030196c48832a5494174 languageName: node linkType: hard @@ -23006,7 +23006,7 @@ __metadata: languageName: node linkType: hard -"watchpack@npm:^2.3.1": +"watchpack@npm:^2.4.0": version: 2.4.0 resolution: "watchpack@npm:2.4.0" dependencies: @@ -23175,19 +23175,19 @@ __metadata: linkType: hard "webpack@npm:^5.64.4": - version: 5.73.0 - resolution: "webpack@npm:5.73.0" + version: 5.76.3 + resolution: "webpack@npm:5.76.3" dependencies: "@types/eslint-scope": ^3.7.3 "@types/estree": ^0.0.51 "@webassemblyjs/ast": 1.11.1 "@webassemblyjs/wasm-edit": 1.11.1 "@webassemblyjs/wasm-parser": 1.11.1 - acorn: ^8.4.1 + acorn: ^8.7.1 acorn-import-assertions: ^1.7.6 browserslist: ^4.14.5 chrome-trace-event: ^1.0.2 - enhanced-resolve: ^5.9.3 + enhanced-resolve: ^5.10.0 es-module-lexer: ^0.9.0 eslint-scope: 5.1.1 events: ^3.2.0 @@ -23200,14 +23200,14 @@ __metadata: schema-utils: ^3.1.0 tapable: ^2.1.1 terser-webpack-plugin: ^5.1.3 - watchpack: ^2.3.1 + watchpack: ^2.4.0 webpack-sources: ^3.2.3 peerDependenciesMeta: webpack-cli: optional: true bin: webpack: bin/webpack.js - checksum: aa434a241bad6176b68e1bf0feb1972da4dcbf27cb3d94ae24f6eb31acc37dceb9c4aae55e068edca75817bfe91f13cd20b023ac55d9b1b2f8b66a4037c9468f + checksum: 363f536b56971d056e34ab4cffa4cbc630b220e51be1a8c3adea87d9f0b51c49cfc7c3720d6614a1fd2c8c63f1ab3100db916fe8367c8bb9299327ff8c3f856d languageName: node linkType: hard From 389503d43d7f05cb68944aa014f9f86920824cb4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 1 Nov 2022 19:06:14 +0000 Subject: [PATCH 031/251] Bump @xmldom/xmldom from 0.7.5 to 0.7.8 Bumps [@xmldom/xmldom](https://github.com/xmldom/xmldom) from 0.7.5 to 0.7.8. - [Release notes](https://github.com/xmldom/xmldom/releases) - [Changelog](https://github.com/xmldom/xmldom/blob/master/CHANGELOG.md) - [Commits](https://github.com/xmldom/xmldom/compare/0.7.5...0.7.8) --- updated-dependencies: - dependency-name: "@xmldom/xmldom" dependency-type: indirect ... Signed-off-by: dependabot[bot] --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index cfe7b4625..4481528f6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6614,9 +6614,9 @@ __metadata: linkType: hard "@xmldom/xmldom@npm:^0.7.0": - version: 0.7.5 - resolution: "@xmldom/xmldom@npm:0.7.5" - checksum: 8d7ec35c1ef6183b4f621df08e01d7e61f244fb964a4719025e65fe6ac06fac418919be64fb40fe5908e69158ef728f2d936daa082db326fe04603012b5f2a84 + version: 0.7.8 + resolution: "@xmldom/xmldom@npm:0.7.8" + checksum: 3bae232f9e6e8218a2c4ab4baa0e2afd5d15d070236cd14b36a59261e5bc9ab26ddf919671fd3ed7474552c7528cf659f14993fdb12f26418f568121f8f4385f languageName: node linkType: hard From 301b41a135a1fbc4ae168a7b28b6b2c37aadacfc Mon Sep 17 00:00:00 2001 From: Cam Casher Date: Tue, 18 Apr 2023 12:20:47 -0600 Subject: [PATCH 032/251] updates mock data and integration tests --- .talismanrc | 6 +- packages/api/mock-estimates.json | 32 +- packages/client/src/Config.ts | 27 +- packages/client/stub-server/mockData.json | 591 ++++++------------ packages/integration-tests/package.json | 2 +- packages/integration-tests/tests/app.test.js | 30 +- .../integration-tests/tests/page-model.js | 10 +- .../tests/recommendations.test.js | 66 +- scripts/create-client-mock-data.js | 90 ++- 9 files changed, 362 insertions(+), 492 deletions(-) diff --git a/.talismanrc b/.talismanrc index 7139ac90c..bd35f7729 100644 --- a/.talismanrc +++ b/.talismanrc @@ -124,7 +124,7 @@ fileignoreconfig: - filename: packages/api/estimates.cache.test.json checksum: 47701f39ac93d11ebf30ebc3eb7499971094144b82bbd558aebdaa551d8228f3 - filename: packages/api/mock-estimates.json - checksum: 8e9863c48edd5fb60fd4cb2085f466884b231105bdeb16619fdb72ad63598253 + checksum: 1a811c04be292f000abe28d55a30aa3fc37bd4891e1ecfef8a9122e256f87a06 - filename: packages/api/package.json checksum: 3fd7ec81bfa309f1d01ae0eadb695a418850e671d148715ae4259e14706491f5 - filename: packages/api/src/api.test.ts @@ -279,6 +279,8 @@ fileignoreconfig: checksum: 6c6198c36a6276699d141dd125eb8d8f247f7dc41cc27078332791d50d9a1074 - filename: packages/client/stub-server/footprint.json checksum: 2a1551ebe105d866d121c17029d53c7466b67e2cfc1c71b13b5894ae5bc06b50 +- filename: packages/client/stub-server/mockData.json + checksum: c0e961bc6f9750fb6d304ef1e5650024454d327690a2755da61b755bdf166a85 - filename: packages/client/stub-server/recommendations.json checksum: fd9afa539a5991cb21416293ae9d17a57005b5bcacdd148fe9f4ce0fd3de145e - filename: packages/common/src/Config.ts @@ -351,6 +353,8 @@ fileignoreconfig: checksum: 99c4532676af436031037ef8794891a85cc7a6bb306491f7d96339f91c5fccf5 - filename: packages/create-app/templates/default-app/packages/client/src/utils/helpers/transformData.ts checksum: 0d806eb083f87fba89e6cf56744269e50c76d2fd4a096a30f8ab7d16fcdcb991 +- filename: packages/create-app/templates/default-app/packages/client/stub-server/mockData.json + checksum: 9073338cab91e48f6b789c52f415d8559926d791d80aeb2954cb2fb842202a66 - filename: packages/gcp/src/__tests__/BillingExportTable.test.ts checksum: 806ca071f5f496e5bd682f0474543f9b6e6c7f5d687e8db89cf244371393188f - filename: packages/gcp/src/__tests__/GCPAccount.test.ts diff --git a/packages/api/mock-estimates.json b/packages/api/mock-estimates.json index df296c444..aed37b74c 100644 --- a/packages/api/mock-estimates.json +++ b/packages/api/mock-estimates.json @@ -1,31 +1,31 @@ [ -{"timestamp":"2020-01-01T00:00:00.000Z","serviceEstimates":[{"cloudProvider":"AWS","accountId":"aws account 1","accountName":"aws account 1","serviceName":"ebs","kilowattHours":0.8148394263939371,"co2e":3,"cost":1.9952062784974551,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountId":"aws account 1","accountName":"aws account 1","serviceName":"s3","kilowattHours":0.00010100550904187954,"co2e":5,"cost":2.479801470978538,"region":"us-east-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountId":"aws account 1","accountName":"aws account 1","serviceName":"ec2","kilowattHours":50.933103987612505,"co2e":3,"cost":2.014818576889102,"region":"us-west-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountId":"aws account 2","accountName":"aws account 2","serviceName":"rds","kilowattHours":50.92736661108625,"co2e":2,"cost":2.0552869714092585,"region":"us-west-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountId":"aws account 2","accountName":"aws account 2","serviceName":"lambda","kilowattHours":70.47173348463286,"co2e":5,"cost":1.8418849726705238,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountId":"aws account 0","accountName":"aws account 0","serviceName":"elasticache","kilowattHours":50.51043411548123,"co2e":4,"cost":1.9021326841241781,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"GCP","accountId":"gcp account 3","accountName":"gcp account 3","serviceName":"computeEngine","kilowattHours":57.666701915218034,"co2e":1,"cost":1.611910606014586,"region":"us-east1","usesAverageCPUConstant":false},{"cloudProvider":"AZURE","accountName":"azure account 2","serviceName":"virtualMachines","kilowattHours":50.773157853289746,"co2e":5,"cost":2.2728625664822433,"region":"UK South","usesAverageCPUConstant":false}]} +{"timestamp":"2023-04-01T06:00:00.000Z","serviceEstimates":[{"cloudProvider":"AWS","accountName":"aws account 0","serviceName":"ebs","kilowattHours":0.9099318685999922,"co2e":0.00034492696349833045,"cost":2.0611247334106126,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 0","serviceName":"s3","kilowattHours":0.000986946262424086,"co2e":4.0524803092142913e-7,"cost":2.341122015683861,"region":"us-east-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"ec2","kilowattHours":50.176579830111365,"co2e":0.016165238194127487,"cost":2.4154850519158613,"region":"us-west-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"rds","kilowattHours":50.15554852182787,"co2e":0.01615846260063172,"cost":1.8825350541114,"region":"us-west-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 0","serviceName":"lambda","kilowattHours":72.26453941514701,"co2e":0.027393246691560364,"cost":2.0084835420769656,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"GCP","accountName":"gcp account 4","serviceName":"computeEngine","kilowattHours":68.54797801901817,"co2e":0.03290302944912873,"cost":1.8688785461442674,"region":"us-east1","usesAverageCPUConstant":false},{"cloudProvider":"AZURE","accountName":"azure account 4","serviceName":"virtualMachines","kilowattHours":50.02721869884774,"co2e":0.011256124207240741,"cost":2.2910467204304323,"region":"UK South","usesAverageCPUConstant":false}]} , -{"timestamp":"2020-02-01T00:00:00.000Z","serviceEstimates":[{"cloudProvider":"AWS","accountId":"aws account 1","accountName":"aws account 1","serviceName":"ebs","kilowattHours":0.9119831149849784,"co2e":3,"cost":1.7466850895849164,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountId":"aws account 1","accountName":"aws account 1","serviceName":"s3","kilowattHours":0.00010168764085273696,"co2e":2,"cost":2.0929755865584747,"region":"us-east-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountId":"aws account 2","accountName":"aws account 2","serviceName":"ec2","kilowattHours":50.13033413155082,"co2e":3,"cost":2.030781896188273,"region":"us-west-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountId":"aws account 3","accountName":"aws account 3","serviceName":"rds","kilowattHours":53.81264913939785,"co2e":5,"cost":1.7863504430322306,"region":"us-west-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountId":"aws account 4","accountName":"aws account 4","serviceName":"lambda","kilowattHours":63.45075892152083,"co2e":4,"cost":1.8740007850011755,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountId":"aws account 2","accountName":"aws account 2","serviceName":"elasticache","kilowattHours":71.15138851526754,"co2e":3,"cost":1.6684896105816076,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"GCP","accountId":"gcp account 4","accountName":"gcp account 4","serviceName":"computeEngine","kilowattHours":50.77403726191187,"co2e":4,"cost":1.7839515021969587,"region":"us-east1","usesAverageCPUConstant":false},{"cloudProvider":"AZURE","accountId":"azure account 4","accountName":"azure account 4","serviceName":"virtualMachines","kilowattHours":50.373127573933836,"co2e":2,"cost":1.8199290662411896,"region":"UK South","usesAverageCPUConstant":false}]} +{"timestamp":"2023-03-01T07:00:00.000Z","serviceEstimates":[{"cloudProvider":"AWS","accountName":"aws account 0","serviceName":"ebs","kilowattHours":0.9281382786569483,"co2e":0.00035182844915221075,"cost":2.225771432143803,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"s3","kilowattHours":0.00003270772530525257,"co2e":1.3430053672139147e-8,"cost":1.5028221506352941,"region":"us-east-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"ec2","kilowattHours":50.558103056454314,"co2e":0.016288152387388715,"cost":2.440745140900739,"region":"us-west-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"rds","kilowattHours":62.615457571422965,"co2e":0.02017263411941262,"cost":1.5955486508938537,"region":"us-west-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 0","serviceName":"lambda","kilowattHours":50.903421738830794,"co2e":0.01929590917511685,"cost":2.238206567880293,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"GCP","accountName":"gcp account 1","serviceName":"computeEngine","kilowattHours":65.28200479098749,"co2e":0.0050919963736970235,"cost":1.6177172869839958,"region":"us-west1","usesAverageCPUConstant":false},{"cloudProvider":"AZURE","accountName":"azure account 1","serviceName":"virtualMachines","kilowattHours":61.90994721222564,"co2e":0.013929738122750768,"cost":2.2081554041299234,"region":"UK South","usesAverageCPUConstant":false}]} , -{"timestamp":"2020-03-01T00:00:00.000Z","serviceEstimates":[{"cloudProvider":"AWS","accountId":"aws account 3","accountName":"aws account 3","serviceName":"ebs","kilowattHours":0.847821319053617,"co2e":3,"cost":1.8756121359502171,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountId":"aws account 3","accountName":"aws account 3","serviceName":"s3","kilowattHours":0.000630093974723783,"co2e":3,"cost":2.3698572941873914,"region":"us-east-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountId":"aws account 3","accountName":"aws account 3","serviceName":"ec2","kilowattHours":50.10307382046631,"co2e":4,"cost":2.351022985532233,"region":"us-west-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountId":"aws account 2","accountName":"aws account 2","serviceName":"rds","kilowattHours":50.22293041559674,"co2e":3,"cost":1.5327957463127884,"region":"us-west-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountId":"aws account 3","accountName":"aws account 3","serviceName":"lambda","kilowattHours":68.18586584927158,"co2e":2,"cost":2.205957045144824,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountId":"aws account 1","accountName":"aws account 1","serviceName":"elasticache","kilowattHours":61.44127617897667,"co2e":2,"cost":2.2445063541267265,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"GCP","accountId":"gcp account 4","accountName":"gcp account 4","serviceName":"computeEngine","kilowattHours":68.47759811709409,"co2e":2,"cost":2.026647361534485,"region":"us-east1","usesAverageCPUConstant":false},{"cloudProvider":"AZURE","accountId":"azure account 1","accountName":"azure account 1","serviceName":"virtualMachines","kilowattHours":50.5125618456214,"co2e":2,"cost":1.9833785822656285,"region":"UK South","usesAverageCPUConstant":false}]} +{"timestamp":"2023-02-01T07:00:00.000Z","serviceEstimates":[{"cloudProvider":"AWS","accountName":"aws account 1","serviceName":"ebs","kilowattHours":0.9290466782034781,"co2e":0.0003521727952599142,"cost":1.9170203054445436,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 1","serviceName":"s3","kilowattHours":0.00007414638904524828,"co2e":3.0445100513091304e-8,"cost":2.3496108515083787,"region":"us-east-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"ec2","kilowattHours":50.74719344234976,"co2e":0.016349071069741494,"cost":1.8854738557659787,"region":"us-west-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 4","serviceName":"rds","kilowattHours":62.70792733734538,"co2e":0.02020242482649055,"cost":1.9619661070136278,"region":"us-west-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 2","serviceName":"lambda","kilowattHours":50.18018466997432,"co2e":0.019021752422662495,"cost":1.7366812626585235,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"GCP","accountName":"gcp account 3","serviceName":"computeEngine","kilowattHours":50.642678514609926,"co2e":0.024308485687012764,"cost":2.112304415112801,"region":"us-east1","usesAverageCPUConstant":false},{"cloudProvider":"AZURE","accountName":"azure account 1","serviceName":"virtualMachines","kilowattHours":50.39790599515025,"co2e":0.011339528848908806,"cost":1.844015443986478,"region":"UK South","usesAverageCPUConstant":false}]} , -{"timestamp":"2020-04-01T00:00:00.000Z","serviceEstimates":[{"cloudProvider":"AWS","accountId":"aws account 3","accountName":"aws account 3","serviceName":"ebs","kilowattHours":0.0025963667867079376,"co2e":4,"cost":2.110850189120251,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountId":"aws account 3","accountName":"aws account 3","serviceName":"s3","kilowattHours":0.00038988295545011175,"co2e":5,"cost":1.750590801038299,"region":"us-east-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountId":"aws account 4","accountName":"aws account 4","serviceName":"ec2","kilowattHours":50.58419719553395,"co2e":3,"cost":2.331961666410219,"region":"us-west-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountId":"aws account 1","accountName":"aws account 1","serviceName":"rds","kilowattHours":50.09219672002729,"co2e":1,"cost":2.171231385841728,"region":"us-west-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountId":"aws account 0","accountName":"aws account 0","serviceName":"lambda","kilowattHours":50.34788323768369,"co2e":2,"cost":2.178860527900543,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountId":"aws account 1","accountName":"aws account 1","serviceName":"elasticache","kilowattHours":68.0807086208259,"co2e":1,"cost":1.8790892873060863,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"GCP","accountId":"gcp account 2","accountName":"gcp account 2","serviceName":"computeEngine","kilowattHours":50.876621782936894,"co2e":5,"cost":2.008009566294815,"region":"us-east1","usesAverageCPUConstant":false},{"cloudProvider":"AZURE","accountName":"azure account 2","serviceName":"virtualMachines","kilowattHours":69.14280006058016,"co2e":1,"cost":2.159049075087805,"region":"UK South","usesAverageCPUConstant":false}]} +{"timestamp":"2023-01-01T07:00:00.000Z","serviceEstimates":[{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"ebs","kilowattHours":0.7903104123827822,"co2e":0.0002995821777115289,"cost":1.5941517912292598,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"s3","kilowattHours":0.0008485340026475967,"co2e":3.484148497591244e-7,"cost":2.422188298668747,"region":"us-east-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 2","serviceName":"ec2","kilowattHours":50.571988266018494,"co2e":0.01629262574369838,"cost":1.813087747561918,"region":"us-west-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"rds","kilowattHours":50.54507810450079,"co2e":0.016283956177692707,"cost":2.3595674179586856,"region":"us-west-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 4","serviceName":"lambda","kilowattHours":50.31390279587026,"co2e":0.019072440818927745,"cost":1.6209696239449094,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"GCP","accountName":"gcp account 2","serviceName":"computeEngine","kilowattHours":50.995343212272175,"co2e":0.01290182183270486,"cost":2.291652099168084,"region":"us-west2","usesAverageCPUConstant":false},{"cloudProvider":"AZURE","accountName":"azure account 1","serviceName":"virtualMachines","kilowattHours":64.71563943290937,"co2e":0.014561018872404607,"cost":2.1519820863003964,"region":"UK South","usesAverageCPUConstant":false}]} , -{"timestamp":"2020-05-01T00:00:00.000Z","serviceEstimates":[{"cloudProvider":"AWS","accountId":"aws account 3","accountName":"aws account 3","serviceName":"ebs","kilowattHours":0.3793961762889928,"co2e":5,"cost":1.5660349090236223,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountId":"aws account 2","accountName":"aws account 2","serviceName":"s3","kilowattHours":0.0007881261941471649,"co2e":1,"cost":2.107104300712024,"region":"us-east-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountId":"aws account 3","accountName":"aws account 3","serviceName":"ec2","kilowattHours":50.4099085739953,"co2e":2,"cost":1.7912671435326524,"region":"us-west-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountId":"aws account 1","accountName":"aws account 1","serviceName":"rds","kilowattHours":56.61135475563019,"co2e":3,"cost":2.0547082075494254,"region":"us-west-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountId":"aws account 4","accountName":"aws account 4","serviceName":"lambda","kilowattHours":50.16319007556745,"co2e":1,"cost":2.3442440999125886,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountId":"aws account 4","accountName":"aws account 4","serviceName":"elasticache","kilowattHours":66.08793309572448,"co2e":0,"cost":1.89506691677191,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"GCP","accountId":"gcp account 1","accountName":"gcp account 1","serviceName":"computeEngine","kilowattHours":50.2235650164992,"co2e":0,"cost":1.6300863324887378,"region":"us-east1","usesAverageCPUConstant":false},{"cloudProvider":"AZURE","accountId":"azure account 3","accountName":"azure account 3","serviceName":"virtualMachines","kilowattHours":50.27485053251475,"co2e":3,"cost":1.7666414199697307,"region":"UK South","usesAverageCPUConstant":false}]} +{"timestamp":"2022-12-01T07:00:00.000Z","serviceEstimates":[{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"ebs","kilowattHours":0.616242116061845,"co2e":0.00023359828269344752,"cost":1.7611637388166252,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 2","serviceName":"s3","kilowattHours":0.00009916949627876193,"co2e":4.071978852802988e-8,"cost":2.4426180887131026,"region":"us-east-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 2","serviceName":"ec2","kilowattHours":50.149667680040125,"co2e":0.016156567987475487,"cost":2.2645178139778706,"region":"us-west-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 4","serviceName":"rds","kilowattHours":50.15605062459718,"co2e":0.0161586243615746,"cost":2.345203244211236,"region":"us-west-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 2","serviceName":"lambda","kilowattHours":55.97282211541847,"co2e":0.021217561706469563,"cost":1.6084626580032653,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"GCP","accountName":"gcp account 0","serviceName":"computeEngine","kilowattHours":50.58370656686491,"co2e":0.02428017915209516,"cost":2.002632458514051,"region":"us-east1","usesAverageCPUConstant":false},{"cloudProvider":"AZURE","accountName":"azure account 2","serviceName":"virtualMachines","kilowattHours":50.91095442793027,"co2e":0.01145496474628431,"cost":1.9447149842967881,"region":"UK South","usesAverageCPUConstant":false}]} , -{"timestamp":"2020-06-01T00:00:00.000Z","serviceEstimates":[{"cloudProvider":"AWS","accountId":"aws account 0","accountName":"aws account 0","serviceName":"ebs","kilowattHours":0.35269028620932774,"co2e":2,"cost":2.426536030208615,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountId":"aws account 2","accountName":"aws account 2","serviceName":"s3","kilowattHours":0.00007772936246220752,"co2e":2,"cost":1.9926068310477163,"region":"us-east-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountId":"aws account 4","accountName":"aws account 4","serviceName":"ec2","kilowattHours":50.00593572462313,"co2e":3,"cost":1.9838511921282318,"region":"us-west-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountId":"aws account 3","accountName":"aws account 3","serviceName":"rds","kilowattHours":50.803461347266776,"co2e":3,"cost":2.229003808663734,"region":"us-west-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountId":"aws account 0","accountName":"aws account 0","serviceName":"lambda","kilowattHours":52.62976125953012,"co2e":5,"cost":1.6972395592555778,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountId":"aws account 3","accountName":"aws account 3","serviceName":"elasticache","kilowattHours":50.98807880163021,"co2e":2,"cost":1.6236333213217602,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"GCP","accountId":"gcp account 4","accountName":"gcp account 4","serviceName":"computeEngine","kilowattHours":64.63052755166217,"co2e":0,"cost":2.30459191166866,"region":"us-east1","usesAverageCPUConstant":false},{"cloudProvider":"AZURE","accountId":"azure account 1","accountName":"azure account 1","serviceName":"virtualMachines","kilowattHours":53.31425378242701,"co2e":1,"cost":1.6930870858502807,"region":"UK South","usesAverageCPUConstant":false}]} +{"timestamp":"2022-11-01T06:00:00.000Z","serviceEstimates":[{"cloudProvider":"AWS","accountName":"aws account 2","serviceName":"ebs","kilowattHours":0.8445236465308483,"co2e":0.00032013273416680216,"cost":1.8371062169192403,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 2","serviceName":"s3","kilowattHours":0.0002648754924918617,"co2e":1.0875999622109836e-7,"cost":2.236483110571217,"region":"us-east-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"ec2","kilowattHours":50.37629287099671,"co2e":0.016229579145370397,"cost":2.449362850757937,"region":"us-west-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"rds","kilowattHours":50.52526081119707,"co2e":0.016277571699760927,"cost":2.302982452488826,"region":"us-west-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"lambda","kilowattHours":50.357072062609866,"co2e":0.01908880494970146,"cost":2.3300950152526614,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"GCP","accountName":"gcp account 0","serviceName":"computeEngine","kilowattHours":53.70289639150512,"co2e":0.02577739026792246,"cost":1.8981217535028845,"region":"us-east1","usesAverageCPUConstant":false},{"cloudProvider":"AZURE","accountName":"azure account 4","serviceName":"virtualMachines","kilowattHours":50.82389577797691,"co2e":0.011435376550044804,"cost":1.5108425566125716,"region":"UK South","usesAverageCPUConstant":false}]} , -{"timestamp":"2020-07-01T00:00:00.000Z","serviceEstimates":[{"cloudProvider":"AWS","accountId":"aws account 0","accountName":"aws account 0","serviceName":"ebs","kilowattHours":0.5648293345133781,"co2e":5,"cost":2.3304265345916653,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountId":"aws account 4","accountName":"aws account 4","serviceName":"s3","kilowattHours":0.00005263625919629122,"co2e":2,"cost":1.6038362843689178,"region":"us-east-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountId":"aws account 0","accountName":"aws account 0","serviceName":"ec2","kilowattHours":64.1169430041115,"co2e":2,"cost":1.8957903016901754,"region":"us-west-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountId":"aws account 1","accountName":"aws account 1","serviceName":"rds","kilowattHours":50.69439982629539,"co2e":2,"cost":2.3649041975938676,"region":"us-west-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountId":"aws account 4","accountName":"aws account 4","serviceName":"lambda","kilowattHours":50.69873776324742,"co2e":4,"cost":2.1212012771669846,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountId":"aws account 4","accountName":"aws account 4","serviceName":"elasticache","kilowattHours":50.28175003192596,"co2e":3,"cost":1.7570663458717493,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"GCP","accountId":"gcp account 2","accountName":"gcp account 2","serviceName":"computeEngine","kilowattHours":50.71620115791394,"co2e":5,"cost":2.0052165772682793,"region":"us-east1","usesAverageCPUConstant":false},{"cloudProvider":"AZURE","accountId":"azure account 0","accountName":"azure account 0","serviceName":"virtualMachines","kilowattHours":50.146548832423804,"co2e":5,"cost":2.4727258099898037,"region":"UK South","usesAverageCPUConstant":false}]} +{"timestamp":"2022-10-01T06:00:00.000Z","serviceEstimates":[{"cloudProvider":"AWS","accountName":"aws account 0","serviceName":"ebs","kilowattHours":0.33278898489743725,"co2e":0.00012614998771608664,"cost":2.201012388385645,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 1","serviceName":"s3","kilowattHours":0.00098701927251199,"co2e":4.052780094476032e-7,"cost":1.645103657701107,"region":"us-east-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 4","serviceName":"ec2","kilowattHours":50.904310297531644,"co2e":0.016399688935624875,"cost":2.235818514499144,"region":"us-west-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"rds","kilowattHours":50.12852654254913,"co2e":0.016149757010633425,"cost":1.5786907055484218,"region":"us-west-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 2","serviceName":"lambda","kilowattHours":59.54349975897656,"co2e":0.022571094910135484,"cost":2.324453671310117,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"GCP","accountName":"gcp account 3","serviceName":"computeEngine","kilowattHours":63.69530490695243,"co2e":0.03057374635533717,"cost":2.4104193295098923,"region":"us-east1","usesAverageCPUConstant":false},{"cloudProvider":"AZURE","accountName":"azure account 0","serviceName":"virtualMachines","kilowattHours":65.60925208439582,"co2e":0.014762081718989059,"cost":2.4070137425368685,"region":"UK South","usesAverageCPUConstant":false}]} , -{"timestamp":"2020-08-01T00:00:00.000Z","serviceEstimates":[{"cloudProvider":"AWS","accountId":"aws account 4","accountName":"aws account 4","serviceName":"ebs","kilowattHours":0.7421818551593697,"co2e":3,"cost":2.498136748374863,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountId":"aws account 1","accountName":"aws account 1","serviceName":"s3","kilowattHours":0.0002180651158962681,"co2e":5,"cost":1.7632736153406632,"region":"us-east-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountId":"aws account 0","accountName":"aws account 0","serviceName":"ec2","kilowattHours":54.239336047864754,"co2e":3,"cost":1.9661288302344184,"region":"us-west-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountId":"aws account 0","accountName":"aws account 0","serviceName":"rds","kilowattHours":50.23120235816861,"co2e":2,"cost":2.0142111162898986,"region":"us-west-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountId":"aws account 3","accountName":"aws account 3","serviceName":"lambda","kilowattHours":50.86615400739505,"co2e":0,"cost":2.164622206937599,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountId":"aws account 1","accountName":"aws account 1","serviceName":"elasticache","kilowattHours":51.26659783867282,"co2e":3,"cost":1.6231795006271335,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"GCP","accountId":"gcp account 3","accountName":"gcp account 3","serviceName":"computeEngine","kilowattHours":59.19683162717123,"co2e":5,"cost":1.6906306697783389,"region":"us-east1","usesAverageCPUConstant":false},{"cloudProvider":"AZURE","accountId":"azure account 2","accountName":"azure account 2","serviceName":"virtualMachines","kilowattHours":50.27713613081028,"co2e":0,"cost":1.7863670364270168,"region":"UK South","usesAverageCPUConstant":false}]} +{"timestamp":"2022-09-01T06:00:00.000Z","serviceEstimates":[{"cloudProvider":"AWS","accountName":"aws account 4","serviceName":"ebs","kilowattHours":0.7421818551593697,"co2e":0.00028133813365340714,"cost":2.498136748374863,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 1","serviceName":"s3","kilowattHours":0.0002180651158962681,"co2e":8.953928110793486e-8,"cost":1.7632736153406632,"region":"us-east-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 0","serviceName":"ec2","kilowattHours":54.239336047864754,"co2e":0.017474124176532442,"cost":1.9661288302344184,"region":"us-west-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 0","serviceName":"rds","kilowattHours":50.23120235816861,"co2e":0.016182835770124106,"cost":2.0142111162898986,"region":"us-west-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"lambda","kilowattHours":50.86615400739505,"co2e":0.019281782133429234,"cost":2.164622206937599,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"GCP","accountName":"gcp account 3","serviceName":"computeEngine","kilowattHours":59.19683162717123,"co2e":0.02841447918104219,"cost":1.6906306697783389,"region":"us-east1","usesAverageCPUConstant":false},{"cloudProvider":"AZURE","accountName":"azure account 2","serviceName":"virtualMachines","kilowattHours":50.27713613081028,"co2e":0.011312355629432311,"cost":1.7863670364270168,"region":"UK South","usesAverageCPUConstant":false}]} , -{"timestamp":"2020-09-01T00:00:00.000Z","serviceEstimates":[{"cloudProvider":"AWS","accountId":"aws account 0","accountName":"aws account 0","serviceName":"ebs","kilowattHours":0.33278898489743725,"co2e":3,"cost":2.201012388385645,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountId":"aws account 1","accountName":"aws account 1","serviceName":"s3","kilowattHours":0.00098701927251199,"co2e":4,"cost":1.645103657701107,"region":"us-east-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountId":"aws account 4","accountName":"aws account 4","serviceName":"ec2","kilowattHours":50.904310297531644,"co2e":5,"cost":2.235818514499144,"region":"us-west-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountId":"aws account 3","accountName":"aws account 3","serviceName":"rds","kilowattHours":50.12852654254913,"co2e":2,"cost":1.5786907055484218,"region":"us-west-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountId":"aws account 2","accountName":"aws account 2","serviceName":"lambda","kilowattHours":59.54349975897656,"co2e":1,"cost":2.324453671310117,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountId":"aws account 1","accountName":"aws account 1","serviceName":"elasticache","kilowattHours":69.72713040208887,"co2e":1,"cost":1.8748107561117062,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"GCP","accountId":"gcp account 3","accountName":"gcp account 3","serviceName":"computeEngine","kilowattHours":63.69530490695243,"co2e":4,"cost":2.4104193295098923,"region":"us-east1","usesAverageCPUConstant":false},{"cloudProvider":"AZURE","accountId":"azure account 0","accountName":"azure account 0","serviceName":"virtualMachines","kilowattHours":65.60925208439582,"co2e":3,"cost":2.4070137425368685,"region":"UK South","usesAverageCPUConstant":false}]} +{"timestamp":"2022-08-01T06:00:00.000Z","serviceEstimates":[{"cloudProvider":"AWS","accountName":"aws account 0","serviceName":"ebs","kilowattHours":0.5648293345133781,"co2e":0.00021410929100465172,"cost":2.3304265345916653,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 4","serviceName":"s3","kilowattHours":0.00005263625919629122,"co2e":2.161286911607075e-8,"cost":1.6038362843689178,"region":"us-east-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 0","serviceName":"ec2","kilowattHours":64.1169430041115,"co2e":0.02065636317680559,"cost":1.8957903016901754,"region":"us-west-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 1","serviceName":"rds","kilowattHours":50.69439982629539,"co2e":0.016332062708838108,"cost":2.3649041975938676,"region":"us-west-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 4","serviceName":"lambda","kilowattHours":50.69873776324742,"co2e":0.01921831982517644,"cost":2.1212012771669846,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"GCP","accountName":"gcp account 2","serviceName":"computeEngine","kilowattHours":50.71620115791394,"co2e":0.024343776555798693,"cost":2.0052165772682793,"region":"us-east1","usesAverageCPUConstant":false},{"cloudProvider":"AZURE","accountName":"azure account 0","serviceName":"virtualMachines","kilowattHours":50.146548832423804,"co2e":0.011282973487295355,"cost":2.4727258099898037,"region":"UK South","usesAverageCPUConstant":false}]} , -{"timestamp":"2020-10-01T00:00:00.000Z","serviceEstimates":[{"cloudProvider":"AWS","accountId":"aws account 2","accountName":"aws account 2","serviceName":"ebs","kilowattHours":0.8445236465308483,"co2e":3,"cost":1.8371062169192403,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountId":"aws account 2","accountName":"aws account 2","serviceName":"s3","kilowattHours":0.0002648754924918617,"co2e":0,"cost":2.236483110571217,"region":"us-east-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountId":"aws account 3","accountName":"aws account 3","serviceName":"ec2","kilowattHours":50.37629287099671,"co2e":0,"cost":2.449362850757937,"region":"us-west-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountId":"aws account 3","accountName":"aws account 3","serviceName":"rds","kilowattHours":50.52526081119707,"co2e":2,"cost":2.302982452488826,"region":"us-west-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountId":"aws account 3","accountName":"aws account 3","serviceName":"lambda","kilowattHours":50.357072062609866,"co2e":4,"cost":2.3300950152526614,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountId":"aws account 1","accountName":"aws account 1","serviceName":"elasticache","kilowattHours":66.94984534593364,"co2e":3,"cost":2.4527664313659665,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"GCP","accountId":"gcp account 0","accountName":"gcp account 0","serviceName":"computeEngine","kilowattHours":53.70289639150512,"co2e":0,"cost":1.8981217535028845,"region":"us-east1","usesAverageCPUConstant":false},{"cloudProvider":"AZURE","accountId":"azure account 4","accountName":"azure account 4","serviceName":"virtualMachines","kilowattHours":50.82389577797691,"co2e":3,"cost":1.5108425566125716,"region":"UK South","usesAverageCPUConstant":false}]} +{"timestamp":"2022-07-01T06:00:00.000Z","serviceEstimates":[{"cloudProvider":"AWS","accountName":"aws account 0","serviceName":"ebs","kilowattHours":0.35269028620932774,"co2e":0.00013369395410308364,"cost":2.426536030208615,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 2","serviceName":"s3","kilowattHours":0.00007772936246220752,"co2e":3.1916298061882105e-8,"cost":1.9926068310477163,"region":"us-east-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 4","serviceName":"ec2","kilowattHours":50.00593572462313,"co2e":0.016110262294594658,"cost":1.9838511921282318,"region":"us-west-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"rds","kilowattHours":50.803461347266776,"co2e":0.016367198731864895,"cost":2.229003808663734,"region":"us-west-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 0","serviceName":"lambda","kilowattHours":52.62976125953012,"co2e":0.019950310970888823,"cost":1.6972395592555778,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"GCP","accountName":"gcp account 4","serviceName":"computeEngine","kilowattHours":64.63052755166217,"co2e":0.03102265322479784,"cost":2.30459191166866,"region":"us-east1","usesAverageCPUConstant":false},{"cloudProvider":"AZURE","accountName":"azure account 1","serviceName":"virtualMachines","kilowattHours":53.31425378242701,"co2e":0.011995707101046077,"cost":1.6930870858502807,"region":"UK South","usesAverageCPUConstant":false}]} , -{"timestamp":"2020-11-01T00:00:00.000Z","serviceEstimates":[{"cloudProvider":"AWS","accountId":"aws account 3","accountName":"aws account 3","serviceName":"ebs","kilowattHours":0.616242116061845,"co2e":3,"cost":1.7611637388166252,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountId":"aws account 2","accountName":"aws account 2","serviceName":"s3","kilowattHours":0.00009916949627876193,"co2e":0,"cost":2.4426180887131026,"region":"us-east-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountId":"aws account 2","accountName":"aws account 2","serviceName":"ec2","kilowattHours":50.149667680040125,"co2e":5,"cost":2.2645178139778706,"region":"us-west-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountId":"aws account 4","accountName":"aws account 4","serviceName":"rds","kilowattHours":50.15605062459718,"co2e":1,"cost":2.345203244211236,"region":"us-west-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountId":"aws account 2","accountName":"aws account 2","serviceName":"lambda","kilowattHours":55.97282211541847,"co2e":2,"cost":1.6084626580032653,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountId":"aws account 0","accountName":"aws account 0","serviceName":"elasticache","kilowattHours":50.082598224165906,"co2e":1,"cost":2.051192241648004,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"GCP","accountId":"gcp account 0","accountName":"gcp account 0","serviceName":"computeEngine","kilowattHours":50.58370656686491,"co2e":2,"cost":2.002632458514051,"region":"us-east1","usesAverageCPUConstant":false},{"cloudProvider":"AZURE","accountId":"azure account 2","accountName":"azure account 2","serviceName":"virtualMachines","kilowattHours":50.91095442793027,"co2e":4,"cost":1.9447149842967881,"region":"UK South","usesAverageCPUConstant":false}]} +{"timestamp":"2022-06-01T06:00:00.000Z","serviceEstimates":[{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"ebs","kilowattHours":0.3793961762889928,"co2e":0.00014381732914969223,"cost":1.5660349090236223,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 2","serviceName":"s3","kilowattHours":0.0007881261941471649,"co2e":3.2361092032637913e-7,"cost":2.107104300712024,"region":"us-east-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"ec2","kilowattHours":50.4099085739953,"co2e":0.016240409015558344,"cost":1.7912671435326524,"region":"us-west-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 1","serviceName":"rds","kilowattHours":56.61135475563019,"co2e":0.01823831032755711,"cost":2.0547082075494254,"region":"us-west-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 4","serviceName":"lambda","kilowattHours":50.16319007556745,"co2e":0.019015310298755278,"cost":2.3442440999125886,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"GCP","accountName":"gcp account 1","serviceName":"computeEngine","kilowattHours":50.2235650164992,"co2e":0.02410731120791962,"cost":1.6300863324887378,"region":"us-east1","usesAverageCPUConstant":false},{"cloudProvider":"AZURE","accountName":"azure account 3","serviceName":"virtualMachines","kilowattHours":50.27485053251475,"co2e":0.011311841369815818,"cost":1.7666414199697307,"region":"UK South","usesAverageCPUConstant":false}]} , -{"timestamp":"2020-12-01T00:00:00.000Z","serviceEstimates":[{"cloudProvider":"AWS","accountId":"aws account 3","accountName":"aws account 3","serviceName":"ebs","kilowattHours":0.7903104123827822,"co2e":0,"cost":1.5941517912292598,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountId":"aws account 3","accountName":"aws account 3","serviceName":"s3","kilowattHours":0.0008485340026475967,"co2e":5,"cost":2.422188298668747,"region":"us-east-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountId":"aws account 2","accountName":"aws account 2","serviceName":"ec2","kilowattHours":50.571988266018494,"co2e":0,"cost":1.813087747561918,"region":"us-west-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountId":"aws account 3","accountName":"aws account 3","serviceName":"rds","kilowattHours":50.54507810450079,"co2e":0,"cost":2.3595674179586856,"region":"us-west-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountId":"aws account 4","accountName":"aws account 4","serviceName":"lambda","kilowattHours":50.31390279587026,"co2e":2,"cost":1.6209696239449094,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountId":"aws account 2","accountName":"aws account 2","serviceName":"elasticache","kilowattHours":72.80796713063573,"co2e":1,"cost":1.7561720671460492,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"GCP","accountId":"gcp account 2","accountName":"gcp account 2","serviceName":"computeEngine","kilowattHours":50.995343212272175,"co2e":5,"cost":2.291652099168084,"region":"us-east1","usesAverageCPUConstant":false},{"cloudProvider":"AZURE","accountId":"azure account 1","accountName":"azure account 1","serviceName":"virtualMachines","kilowattHours":64.71563943290937,"co2e":4,"cost":2.1519820863003964,"region":"UK South","usesAverageCPUConstant":false}]} +{"timestamp":"2022-05-01T06:00:00.000Z","serviceEstimates":[{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"ebs","kilowattHours":0.0025963667867079376,"co2e":9.842021614705911e-7,"cost":2.110850189120251,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"s3","kilowattHours":0.00038988295545011175,"co2e":1.600890605714595e-7,"cost":1.750590801038299,"region":"us-east-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 4","serviceName":"ec2","kilowattHours":50.58419719553395,"co2e":0.016296559057893588,"cost":2.331961666410219,"region":"us-west-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 1","serviceName":"rds","kilowattHours":50.09219672002729,"co2e":0.01613805274070103,"cost":2.171231385841728,"region":"us-west-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 0","serviceName":"lambda","kilowattHours":50.34788323768369,"co2e":0.01908532175102552,"cost":2.178860527900543,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"GCP","accountName":"gcp account 2","serviceName":"computeEngine","kilowattHours":50.876621782936894,"co2e":0.02442077845580971,"cost":2.008009566294815,"region":"us-east1","usesAverageCPUConstant":false},{"cloudProvider":"AZURE","accountName":"azure account 2","serviceName":"virtualMachines","kilowattHours":69.14280006058016,"co2e":0.015557130013630535,"cost":2.159049075087805,"region":"UK South","usesAverageCPUConstant":false}]} , -{"timestamp":"2021-01-01T00:00:00.000Z","serviceEstimates":[{"cloudProvider":"AWS","accountId":"aws account 1","accountName":"aws account 1","serviceName":"ebs","kilowattHours":0.9290466782034781,"co2e":0,"cost":1.9170203054445436,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountId":"aws account 1","accountName":"aws account 1","serviceName":"s3","kilowattHours":0.00007414638904524828,"co2e":0,"cost":2.3496108515083787,"region":"us-east-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountId":"aws account 3","accountName":"aws account 3","serviceName":"ec2","kilowattHours":50.74719344234976,"co2e":4,"cost":1.8854738557659787,"region":"us-west-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountId":"aws account 4","accountName":"aws account 4","serviceName":"rds","kilowattHours":62.70792733734538,"co2e":2,"cost":1.9619661070136278,"region":"us-west-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountId":"aws account 2","accountName":"aws account 2","serviceName":"lambda","kilowattHours":50.18018466997432,"co2e":2,"cost":1.7366812626585235,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountId":"aws account 3","accountName":"aws account 3","serviceName":"elasticache","kilowattHours":72.83814860729936,"co2e":0,"cost":2.28301997160316,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"GCP","accountId":"gcp account 3","accountName":"gcp account 3","serviceName":"computeEngine","kilowattHours":50.642678514609926,"co2e":4,"cost":2.112304415112801,"region":"us-east1","usesAverageCPUConstant":false},{"cloudProvider":"AZURE","accountId":"azure account 1","accountName":"azure account 1","serviceName":"virtualMachines","kilowattHours":50.39790599515025,"co2e":5,"cost":1.844015443986478,"region":"UK South","usesAverageCPUConstant":false}]} +{"timestamp":"2022-04-01T06:00:00.000Z","serviceEstimates":[{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"ebs","kilowattHours":0.847821319053617,"co2e":0.00032138277959233556,"cost":1.8756121359502171,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"s3","kilowattHours":0.000630093974723783,"co2e":2.587216267733831e-7,"cost":2.3698572941873914,"region":"us-east-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"ec2","kilowattHours":50.10307382046631,"co2e":0.016141556983518168,"cost":2.351022985532233,"region":"us-west-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 2","serviceName":"rds","kilowattHours":50.22293041559674,"co2e":0.016180170823201556,"cost":1.5327957463127884,"region":"us-west-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"lambda","kilowattHours":68.18586584927158,"co2e":0.025847147981617528,"cost":2.205957045144824,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"GCP","accountName":"gcp account 4","serviceName":"computeEngine","kilowattHours":68.47759811709409,"co2e":0.032869247096205166,"cost":2.026647361534485,"region":"us-east1","usesAverageCPUConstant":false},{"cloudProvider":"AZURE","accountName":"azure account 1","serviceName":"virtualMachines","kilowattHours":50.5125618456214,"co2e":0.011365326415264814,"cost":1.9833785822656285,"region":"UK South","usesAverageCPUConstant":false}]} , -{"timestamp":"2021-02-01T00:00:00.000Z","serviceEstimates":[{"cloudProvider":"AWS","accountId":"aws account 0","accountName":"aws account 0","serviceName":"ebs","kilowattHours":0.9281382786569483,"co2e":2,"cost":2.225771432143803,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountId":"aws account 3","accountName":"aws account 3","serviceName":"s3","kilowattHours":0.00003270772530525257,"co2e":4,"cost":1.5028221506352941,"region":"us-east-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountId":"aws account 3","accountName":"aws account 3","serviceName":"ec2","kilowattHours":50.558103056454314,"co2e":0,"cost":2.440745140900739,"region":"us-west-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountId":"aws account 3","accountName":"aws account 3","serviceName":"rds","kilowattHours":62.615457571422965,"co2e":0,"cost":1.5955486508938537,"region":"us-west-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountId":"aws account 0","accountName":"aws account 0","serviceName":"lambda","kilowattHours":50.903421738830794,"co2e":1,"cost":2.238206567880293,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountId":"aws account 3","accountName":"aws account 3","serviceName":"elasticache","kilowattHours":50.25001144079886,"co2e":5,"cost":2.293443645415854,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"GCP","accountId":"gcp account 1","accountName":"gcp account 1","serviceName":"computeEngine","kilowattHours":65.28200479098749,"co2e":3,"cost":1.6177172869839958,"region":"us-east1","usesAverageCPUConstant":false},{"cloudProvider":"AZURE","accountId":"azure account 1","accountName":"azure account 1","serviceName":"virtualMachines","kilowattHours":61.90994721222564,"co2e":2,"cost":2.2081554041299234,"region":"UK South","usesAverageCPUConstant":false}]} +{"timestamp":"2022-03-01T07:00:00.000Z","serviceEstimates":[{"cloudProvider":"AWS","accountName":"aws account 1","serviceName":"ebs","kilowattHours":0.9119831149849784,"co2e":0.00034570452741424076,"cost":1.7466850895849164,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 1","serviceName":"s3","kilowattHours":0.00010168764085273696,"co2e":4.175375883526062e-8,"cost":2.0929755865584747,"region":"us-east-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 2","serviceName":"ec2","kilowattHours":50.13033413155082,"co2e":0.01615033935615933,"cost":2.030781896188273,"region":"us-west-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"rds","kilowattHours":53.81264913939785,"co2e":0.017336659735292387,"cost":1.7863504430322306,"region":"us-west-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 4","serviceName":"lambda","kilowattHours":63.45075892152083,"co2e":0.02405221573362198,"cost":1.8740007850011755,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"GCP","accountName":"gcp account 4","serviceName":"computeEngine","kilowattHours":50.77403726191187,"co2e":0.0243715378857177,"cost":1.7839515021969587,"region":"us-east1","usesAverageCPUConstant":false},{"cloudProvider":"AZURE","accountName":"azure account 4","serviceName":"virtualMachines","kilowattHours":50.373127573933836,"co2e":0.011333953704135112,"cost":1.8199290662411896,"region":"UK South","usesAverageCPUConstant":false}]} , -{"timestamp":"2021-03-01T00:00:00.000Z","serviceEstimates":[{"cloudProvider":"AWS","accountId":"aws account 0","accountName":"aws account 0","serviceName":"ebs","kilowattHours":0.9099318685999922,"co2e":2,"cost":2.0611247334106126,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountId":"aws account 0","accountName":"aws account 0","serviceName":"s3","kilowattHours":0.000986946262424086,"co2e":1,"cost":2.341122015683861,"region":"us-east-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountId":"aws account 3","accountName":"aws account 3","serviceName":"ec2","kilowattHours":50.176579830111365,"co2e":3,"cost":2.4154850519158613,"region":"us-west-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountId":"aws account 3","accountName":"aws account 3","serviceName":"rds","kilowattHours":50.15554852182787,"co2e":4,"cost":1.8825350541114,"region":"us-west-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountId":"aws account 0","accountName":"aws account 0","serviceName":"lambda","kilowattHours":72.26453941514701,"co2e":2,"cost":2.0084835420769656,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountId":"aws account 4","accountName":"aws account 4","serviceName":"elasticache","kilowattHours":50.727903398223255,"co2e":5,"cost":1.9935817888548006,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"GCP","accountId":"gcp account 4","accountName":"gcp account 4","serviceName":"computeEngine","kilowattHours":68.54797801901817,"co2e":4,"cost":1.8688785461442674,"region":"us-east1","usesAverageCPUConstant":false},{"cloudProvider":"AZURE","accountId":"azure account 4","accountName":"azure account 4","serviceName":"virtualMachines","kilowattHours":50.02721869884774,"co2e":2,"cost":2.2910467204304323,"region":"UK South","usesAverageCPUConstant":false}]} -] +{"timestamp":"2022-02-01T07:00:00.000Z","serviceEstimates":[{"cloudProvider":"AWS","accountName":"aws account 1","serviceName":"ebs","kilowattHours":0.8148394263939371,"co2e":0.00030888036652372336,"cost":1.9952062784974551,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 1","serviceName":"s3","kilowattHours":0.00010100550904187954,"co2e":4.147367005666808e-8,"cost":2.479801470978538,"region":"us-east-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 1","serviceName":"ec2","kilowattHours":50.933103987612505,"co2e":0.016408965312377156,"cost":2.014818576889102,"region":"us-west-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 2","serviceName":"rds","kilowattHours":50.92736661108625,"co2e":0.016407116918993825,"cost":2.0552869714092585,"region":"us-west-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 2","serviceName":"lambda","kilowattHours":70.47173348463286,"co2e":0.026713649540286294,"cost":1.8418849726705238,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"GCP","accountName":"gcp account 3","serviceName":"computeEngine","kilowattHours":57.666701915218034,"co2e":0.02768001691930466,"cost":1.611910606014586,"region":"us-east1","usesAverageCPUConstant":false},{"cloudProvider":"AZURE","accountName":"azure account 2","serviceName":"virtualMachines","kilowattHours":50.773157853289746,"co2e":0.011423960516990192,"cost":2.2728625664822433,"region":"UK South","usesAverageCPUConstant":false}]} +] \ No newline at end of file diff --git a/packages/client/src/Config.ts b/packages/client/src/Config.ts index 119839c3b..b2cacaaf9 100644 --- a/packages/client/src/Config.ts +++ b/packages/client/src/Config.ts @@ -19,13 +19,27 @@ export interface ClientConfig { START_DATE: string END_DATE: string DISABLE_CACHE: boolean + TEST_MODE: boolean } -const previousYearOfUsage = +let previousYearOfUsage = !!process.env.REACT_APP_PREVIOUS_YEAR_OF_USAGE && process.env.REACT_APP_PREVIOUS_YEAR_OF_USAGE !== 'false' +let groupBy = process.env.REACT_APP_GROUP_BY || 'day' +let pageLimit = process.env.REACT_APP_PAGE_LIMIT || '50000' +let baseUrl = process.env.REACT_APP_BASE_URL || '/api' +let endDate = process.env.REACT_APP_END_DATE +let minDateAge = process.env.REACT_APP_MINIMAL_DATE_AGE || '0' -const groupBy = process.env.REACT_APP_GROUP_BY || 'day' +// For local development / integration testing +if (process.env.REACT_APP_TEST_MODE === 'true') { + previousYearOfUsage = true + groupBy = 'month' + pageLimit = '1000' + baseUrl = 'http://127.0.0.1:3000/api' + endDate = null + minDateAge = '0' +} const appConfig: ClientConfig = { CURRENT_PROVIDERS: [ @@ -39,12 +53,13 @@ const appConfig: ClientConfig = { TYPE: process.env.REACT_APP_DATE_RANGE_TYPE || 'months', }, GROUP_BY: groupBy, - PAGE_LIMIT: process.env.REACT_APP_PAGE_LIMIT || '50000', - BASE_URL: process.env.REACT_APP_BASE_URL || '/api', - MINIMAL_DATE_AGE: process.env.REACT_APP_MINIMAL_DATE_AGE || '0', + PAGE_LIMIT: pageLimit, + BASE_URL: baseUrl, + MINIMAL_DATE_AGE: minDateAge, START_DATE: process.env.REACT_APP_START_DATE, - END_DATE: process.env.REACT_APP_END_DATE, + END_DATE: endDate, DISABLE_CACHE: process.env.REACT_APP_DISABLE_CACHE === 'true', + TEST_MODE: process.env.REACT_APP_TEST_MODE === 'true', } export default appConfig diff --git a/packages/client/stub-server/mockData.json b/packages/client/stub-server/mockData.json index 04ee6f0bc..1ceac5f79 100644 --- a/packages/client/stub-server/mockData.json +++ b/packages/client/stub-server/mockData.json @@ -1,260 +1,77 @@ { "emissions": [ - { - "region": "us-east-1", - "mtPerKwHour": 0.000379069 - }, - { - "region": "us-east-2", - "mtPerKwHour": 0.000410608 - }, - { - "region": "us-west-1", - "mtPerKwHour": 0.000322167 - }, - { - "region": "us-west-2", - "mtPerKwHour": 0.000322167 - }, - { - "region": "us-gov-east-1", - "mtPerKwHour": 0.000379069 - }, - { - "region": "us-gov-west-1", - "mtPerKwHour": 0.000322167 - }, - { - "region": "af-south-1", - "mtPerKwHour": 0.0009006 - }, - { - "region": "ap-east-1", - "mtPerKwHour": 0.00071 - }, - { - "region": "ap-south-1", - "mtPerKwHour": 0.0007082 - }, - { - "region": "ap-northeast-3", - "mtPerKwHour": 0.0004658 - }, - { - "region": "ap-northeast-2", - "mtPerKwHour": 0.0004156 - }, - { - "region": "ap-southeast-1", - "mtPerKwHour": 0.000408 - }, - { - "region": "ap-southeast-2", - "mtPerKwHour": 0.00076 - }, - { - "region": "ap-northeast-1", - "mtPerKwHour": 0.0004658 - }, - { - "region": "ca-central-1", - "mtPerKwHour": 0.00012 - }, - { - "region": "cn-north-1", - "mtPerKwHour": 0.0005374 - }, - { - "region": "cn-northwest-1", - "mtPerKwHour": 0.0005374 - }, - { - "region": "eu-central-1", - "mtPerKwHour": 0.000311 - }, - { - "region": "eu-west-1", - "mtPerKwHour": 0.0002786 - }, - { - "region": "eu-west-2", - "mtPerKwHour": 0.000225 - }, - { - "region": "eu-south-1", - "mtPerKwHour": 0.0002134 - }, - { - "region": "eu-west-3", - "mtPerKwHour": 0.0000511 - }, - { - "region": "eu-north-1", - "mtPerKwHour": 0.0000088 - }, - { - "region": "me-south-1", - "mtPerKwHour": 0.0005059 - }, - { - "region": "sa-east-1", - "mtPerKwHour": 0.0000617 - }, - { - "region": "us-central1", - "mtPerKwHour": 0.000454 - }, - { - "region": "us-central2", - "mtPerKwHour": 0.000454 - }, - { - "region": "us-east1", - "mtPerKwHour": 0.00048 - }, - { - "region": "us-east4", - "mtPerKwHour": 0.000361 - }, - { - "region": "us-west1", - "mtPerKwHour": 0.000078 - }, - { - "region": "us-west2", - "mtPerKwHour": 0.000253 - }, - { - "region": "us-west3", - "mtPerKwHour": 0.000533 - }, - { - "region": "us-west4", - "mtPerKwHour": 0.000455 - }, - { - "region": "asia-east1", - "mtPerKwHour": 0.00054 - }, - { - "region": "asia-east2", - "mtPerKwHour": 0.000453 - }, - { - "region": "asia-northeast1", - "mtPerKwHour": 0.000554 - }, - { - "region": "asia-northeast2", - "mtPerKwHour": 0.000442 - }, - { - "region": "asia-northeast3", - "mtPerKwHour": 0.000457 - }, - { - "region": "asia-south1", - "mtPerKwHour": 0.000721 - }, - { - "region": "asia-southeast1", - "mtPerKwHour": 0.000493 - }, - { - "region": "asia-southeast2", - "mtPerKwHour": 0.000647 - }, - { - "region": "australia-southeast1", - "mtPerKwHour": 0.000727 - }, - { - "region": "europe-north1", - "mtPerKwHour": 0.000133 - }, - { - "region": "europe-west1", - "mtPerKwHour": 0.000212 - }, - { - "region": "europe-west2", - "mtPerKwHour": 0.000231 - }, - { - "region": "europe-west3", - "mtPerKwHour": 0.000293 - }, - { - "region": "europe-west4", - "mtPerKwHour": 0.00041 - }, - { - "region": "europe-west6", - "mtPerKwHour": 0.000087 - }, - { - "region": "northamerica-northeast1", - "mtPerKwHour": 0.000027 - }, - { - "region": "southamerica-east1", - "mtPerKwHour": 0.000103 - }, - { - "region": "AP East", - "mtPerKwHour": 0.00071 - }, - { - "region": "EU West", - "mtPerKwHour": 0.0003284 - }, - { - "region": "IN Central", - "mtPerKwHour": 0.0007082 - }, - { - "region": "UK South", - "mtPerKwHour": 0.000225 - }, - { - "region": "UK West", - "mtPerKwHour": 0.000225 - }, - { - "region": "US Central", - "mtPerKwHour": 0.000426254 - }, - { - "region": "US East", - "mtPerKwHour": 0.000379069 - }, - { - "region": "US South Central", - "mtPerKwHour": 0.000373231 - }, - { - "region": "US West", - "mtPerKwHour": 0.000322167 - }, - { - "region": "US West 2", - "mtPerKwHour": 0.000322167 - }, - { - "region": "Unknown", - "mtPerKwHour": 0.0003852304903666667 - } + { "region": "us-east-1", "mtPerKwHour": 0.000379069 }, + { "region": "us-east-2", "mtPerKwHour": 0.000410608 }, + { "region": "us-west-1", "mtPerKwHour": 0.000322167 }, + { "region": "us-west-2", "mtPerKwHour": 0.000322167 }, + { "region": "us-gov-east-1", "mtPerKwHour": 0.000379069 }, + { "region": "us-gov-west-1", "mtPerKwHour": 0.000322167 }, + { "region": "af-south-1", "mtPerKwHour": 0.0009006 }, + { "region": "ap-east-1", "mtPerKwHour": 0.00071 }, + { "region": "ap-south-1", "mtPerKwHour": 0.0007082 }, + { "region": "ap-northeast-3", "mtPerKwHour": 0.0004658 }, + { "region": "ap-northeast-2", "mtPerKwHour": 0.0004156 }, + { "region": "ap-southeast-1", "mtPerKwHour": 0.000408 }, + { "region": "ap-southeast-2", "mtPerKwHour": 0.00076 }, + { "region": "ap-northeast-1", "mtPerKwHour": 0.0004658 }, + { "region": "ca-central-1", "mtPerKwHour": 0.00012 }, + { "region": "cn-north-1", "mtPerKwHour": 0.0005374 }, + { "region": "cn-northwest-1", "mtPerKwHour": 0.0005374 }, + { "region": "eu-central-1", "mtPerKwHour": 0.000311 }, + { "region": "eu-west-1", "mtPerKwHour": 0.0002786 }, + { "region": "eu-west-2", "mtPerKwHour": 0.000225 }, + { "region": "eu-south-1", "mtPerKwHour": 0.0002134 }, + { "region": "eu-west-3", "mtPerKwHour": 0.0000511 }, + { "region": "eu-north-1", "mtPerKwHour": 0.0000088 }, + { "region": "me-south-1", "mtPerKwHour": 0.0005059 }, + { "region": "sa-east-1", "mtPerKwHour": 0.0000617 }, + { "region": "us-central1", "mtPerKwHour": 0.000454 }, + { "region": "us-central2", "mtPerKwHour": 0.000454 }, + { "region": "us-east1", "mtPerKwHour": 0.00048 }, + { "region": "us-east4", "mtPerKwHour": 0.000361 }, + { "region": "us-west1", "mtPerKwHour": 0.000078 }, + { "region": "us-west2", "mtPerKwHour": 0.000253 }, + { "region": "us-west3", "mtPerKwHour": 0.000533 }, + { "region": "us-west4", "mtPerKwHour": 0.000455 }, + { "region": "asia-east1", "mtPerKwHour": 0.00054 }, + { "region": "asia-east2", "mtPerKwHour": 0.000453 }, + { "region": "asia-northeast1", "mtPerKwHour": 0.000554 }, + { "region": "asia-northeast2", "mtPerKwHour": 0.000442 }, + { "region": "asia-northeast3", "mtPerKwHour": 0.000457 }, + { "region": "asia-south1", "mtPerKwHour": 0.000721 }, + { "region": "asia-southeast1", "mtPerKwHour": 0.000493 }, + { "region": "asia-southeast2", "mtPerKwHour": 0.000647 }, + { "region": "australia-southeast1", "mtPerKwHour": 0.000727 }, + { "region": "europe-north1", "mtPerKwHour": 0.000133 }, + { "region": "europe-west1", "mtPerKwHour": 0.000212 }, + { "region": "europe-west2", "mtPerKwHour": 0.000231 }, + { "region": "europe-west3", "mtPerKwHour": 0.000293 }, + { "region": "europe-west4", "mtPerKwHour": 0.00041 }, + { "region": "europe-west6", "mtPerKwHour": 0.000087 }, + { "region": "northamerica-northeast1", "mtPerKwHour": 0.000027 }, + { "region": "southamerica-east1", "mtPerKwHour": 0.000103 }, + { "region": "AP East", "mtPerKwHour": 0.00071 }, + { "region": "EU West", "mtPerKwHour": 0.0003284 }, + { "region": "IN Central", "mtPerKwHour": 0.0007082 }, + { "region": "UK South", "mtPerKwHour": 0.000225 }, + { "region": "UK West", "mtPerKwHour": 0.000225 }, + { "region": "US Central", "mtPerKwHour": 0.000426254 }, + { "region": "US East", "mtPerKwHour": 0.000379069 }, + { "region": "US South Central", "mtPerKwHour": 0.000373231 }, + { "region": "US West", "mtPerKwHour": 0.000322167 }, + { "region": "US West 2", "mtPerKwHour": 0.000322167 }, + { "region": "Unknown", "mtPerKwHour": 0.0003852304903666667 } ], "footprint": [ { - "timestamp": "2021-03-25T00:00:00.000Z", + "timestamp": "2023-04-01T06:00:00.000Z", "serviceEstimates": [ { "cloudProvider": "AWS", "accountName": "aws account 0", "serviceName": "ebs", "kilowattHours": 0.9099318685999922, - "co2e": 2, + "co2e": 0.00034492696349833045, "cost": 2.0611247334106126, "region": "us-east-1", "usesAverageCPUConstant": false @@ -264,7 +81,7 @@ "accountName": "aws account 0", "serviceName": "s3", "kilowattHours": 0.000986946262424086, - "co2e": 1, + "co2e": 4.0524803092142913e-7, "cost": 2.341122015683861, "region": "us-east-2", "usesAverageCPUConstant": false @@ -274,7 +91,7 @@ "accountName": "aws account 3", "serviceName": "ec2", "kilowattHours": 50.176579830111365, - "co2e": 3, + "co2e": 0.016165238194127487, "cost": 2.4154850519158613, "region": "us-west-1", "usesAverageCPUConstant": false @@ -284,7 +101,7 @@ "accountName": "aws account 3", "serviceName": "rds", "kilowattHours": 50.15554852182787, - "co2e": 4, + "co2e": 0.01615846260063172, "cost": 1.8825350541114, "region": "us-west-2", "usesAverageCPUConstant": false @@ -294,7 +111,7 @@ "accountName": "aws account 0", "serviceName": "lambda", "kilowattHours": 72.26453941514701, - "co2e": 2, + "co2e": 0.027393246691560364, "cost": 2.0084835420769656, "region": "us-east-1", "usesAverageCPUConstant": false @@ -304,7 +121,7 @@ "accountName": "gcp account 4", "serviceName": "computeEngine", "kilowattHours": 68.54797801901817, - "co2e": 4, + "co2e": 0.03290302944912873, "cost": 1.8688785461442674, "region": "us-east1", "usesAverageCPUConstant": false @@ -314,7 +131,7 @@ "accountName": "azure account 4", "serviceName": "virtualMachines", "kilowattHours": 50.02721869884774, - "co2e": 2, + "co2e": 0.011256124207240741, "cost": 2.2910467204304323, "region": "UK South", "usesAverageCPUConstant": false @@ -322,14 +139,14 @@ ] }, { - "timestamp": "2021-02-25T00:00:00.000Z", + "timestamp": "2023-03-01T07:00:00.000Z", "serviceEstimates": [ { "cloudProvider": "AWS", "accountName": "aws account 0", "serviceName": "ebs", "kilowattHours": 0.9281382786569483, - "co2e": 2, + "co2e": 0.00035182844915221075, "cost": 2.225771432143803, "region": "us-east-1", "usesAverageCPUConstant": false @@ -339,7 +156,7 @@ "accountName": "aws account 3", "serviceName": "s3", "kilowattHours": 0.00003270772530525257, - "co2e": 4, + "co2e": 1.3430053672139147e-8, "cost": 1.5028221506352941, "region": "us-east-2", "usesAverageCPUConstant": false @@ -349,7 +166,7 @@ "accountName": "aws account 3", "serviceName": "ec2", "kilowattHours": 50.558103056454314, - "co2e": 0, + "co2e": 0.016288152387388715, "cost": 2.440745140900739, "region": "us-west-1", "usesAverageCPUConstant": false @@ -359,7 +176,7 @@ "accountName": "aws account 3", "serviceName": "rds", "kilowattHours": 62.615457571422965, - "co2e": 0, + "co2e": 0.02017263411941262, "cost": 1.5955486508938537, "region": "us-west-2", "usesAverageCPUConstant": false @@ -369,7 +186,7 @@ "accountName": "aws account 0", "serviceName": "lambda", "kilowattHours": 50.903421738830794, - "co2e": 1, + "co2e": 0.01929590917511685, "cost": 2.238206567880293, "region": "us-east-1", "usesAverageCPUConstant": false @@ -379,7 +196,7 @@ "accountName": "gcp account 1", "serviceName": "computeEngine", "kilowattHours": 65.28200479098749, - "co2e": 3, + "co2e": 0.0050919963736970235, "cost": 1.6177172869839958, "region": "us-west1", "usesAverageCPUConstant": false @@ -389,7 +206,7 @@ "accountName": "azure account 1", "serviceName": "virtualMachines", "kilowattHours": 61.90994721222564, - "co2e": 2, + "co2e": 0.013929738122750768, "cost": 2.2081554041299234, "region": "UK South", "usesAverageCPUConstant": false @@ -397,14 +214,14 @@ ] }, { - "timestamp": "2021-01-25T00:00:00.000Z", + "timestamp": "2023-02-01T07:00:00.000Z", "serviceEstimates": [ { "cloudProvider": "AWS", "accountName": "aws account 1", "serviceName": "ebs", "kilowattHours": 0.9290466782034781, - "co2e": 0, + "co2e": 0.0003521727952599142, "cost": 1.9170203054445436, "region": "us-east-1", "usesAverageCPUConstant": false @@ -414,7 +231,7 @@ "accountName": "aws account 1", "serviceName": "s3", "kilowattHours": 0.00007414638904524828, - "co2e": 0, + "co2e": 3.0445100513091304e-8, "cost": 2.3496108515083787, "region": "us-east-2", "usesAverageCPUConstant": false @@ -424,7 +241,7 @@ "accountName": "aws account 3", "serviceName": "ec2", "kilowattHours": 50.74719344234976, - "co2e": 4, + "co2e": 0.016349071069741494, "cost": 1.8854738557659787, "region": "us-west-1", "usesAverageCPUConstant": false @@ -434,7 +251,7 @@ "accountName": "aws account 4", "serviceName": "rds", "kilowattHours": 62.70792733734538, - "co2e": 2, + "co2e": 0.02020242482649055, "cost": 1.9619661070136278, "region": "us-west-2", "usesAverageCPUConstant": false @@ -444,7 +261,7 @@ "accountName": "aws account 2", "serviceName": "lambda", "kilowattHours": 50.18018466997432, - "co2e": 2, + "co2e": 0.019021752422662495, "cost": 1.7366812626585235, "region": "us-east-1", "usesAverageCPUConstant": false @@ -454,7 +271,7 @@ "accountName": "gcp account 3", "serviceName": "computeEngine", "kilowattHours": 50.642678514609926, - "co2e": 4, + "co2e": 0.024308485687012764, "cost": 2.112304415112801, "region": "us-east1", "usesAverageCPUConstant": false @@ -464,7 +281,7 @@ "accountName": "azure account 1", "serviceName": "virtualMachines", "kilowattHours": 50.39790599515025, - "co2e": 5, + "co2e": 0.011339528848908806, "cost": 1.844015443986478, "region": "UK South", "usesAverageCPUConstant": false @@ -472,14 +289,14 @@ ] }, { - "timestamp": "2020-12-25T00:00:00.000Z", + "timestamp": "2023-01-01T07:00:00.000Z", "serviceEstimates": [ { "cloudProvider": "AWS", "accountName": "aws account 3", "serviceName": "ebs", "kilowattHours": 0.7903104123827822, - "co2e": 0, + "co2e": 0.0002995821777115289, "cost": 1.5941517912292598, "region": "us-east-1", "usesAverageCPUConstant": false @@ -489,7 +306,7 @@ "accountName": "aws account 3", "serviceName": "s3", "kilowattHours": 0.0008485340026475967, - "co2e": 5, + "co2e": 3.484148497591244e-7, "cost": 2.422188298668747, "region": "us-east-2", "usesAverageCPUConstant": false @@ -499,7 +316,7 @@ "accountName": "aws account 2", "serviceName": "ec2", "kilowattHours": 50.571988266018494, - "co2e": 0, + "co2e": 0.01629262574369838, "cost": 1.813087747561918, "region": "us-west-1", "usesAverageCPUConstant": false @@ -509,7 +326,7 @@ "accountName": "aws account 3", "serviceName": "rds", "kilowattHours": 50.54507810450079, - "co2e": 0, + "co2e": 0.016283956177692707, "cost": 2.3595674179586856, "region": "us-west-2", "usesAverageCPUConstant": false @@ -519,7 +336,7 @@ "accountName": "aws account 4", "serviceName": "lambda", "kilowattHours": 50.31390279587026, - "co2e": 2, + "co2e": 0.019072440818927745, "cost": 1.6209696239449094, "region": "us-east-1", "usesAverageCPUConstant": false @@ -529,7 +346,7 @@ "accountName": "gcp account 2", "serviceName": "computeEngine", "kilowattHours": 50.995343212272175, - "co2e": 5, + "co2e": 0.01290182183270486, "cost": 2.291652099168084, "region": "us-west2", "usesAverageCPUConstant": false @@ -539,7 +356,7 @@ "accountName": "azure account 1", "serviceName": "virtualMachines", "kilowattHours": 64.71563943290937, - "co2e": 4, + "co2e": 0.014561018872404607, "cost": 2.1519820863003964, "region": "UK South", "usesAverageCPUConstant": false @@ -547,14 +364,14 @@ ] }, { - "timestamp": "2020-11-25T00:00:00.000Z", + "timestamp": "2022-12-01T07:00:00.000Z", "serviceEstimates": [ { "cloudProvider": "AWS", "accountName": "aws account 3", "serviceName": "ebs", "kilowattHours": 0.616242116061845, - "co2e": 3, + "co2e": 0.00023359828269344752, "cost": 1.7611637388166252, "region": "us-east-1", "usesAverageCPUConstant": false @@ -564,7 +381,7 @@ "accountName": "aws account 2", "serviceName": "s3", "kilowattHours": 0.00009916949627876193, - "co2e": 0, + "co2e": 4.071978852802988e-8, "cost": 2.4426180887131026, "region": "us-east-2", "usesAverageCPUConstant": false @@ -574,7 +391,7 @@ "accountName": "aws account 2", "serviceName": "ec2", "kilowattHours": 50.149667680040125, - "co2e": 5, + "co2e": 0.016156567987475487, "cost": 2.2645178139778706, "region": "us-west-1", "usesAverageCPUConstant": false @@ -584,7 +401,7 @@ "accountName": "aws account 4", "serviceName": "rds", "kilowattHours": 50.15605062459718, - "co2e": 1, + "co2e": 0.0161586243615746, "cost": 2.345203244211236, "region": "us-west-2", "usesAverageCPUConstant": false @@ -594,7 +411,7 @@ "accountName": "aws account 2", "serviceName": "lambda", "kilowattHours": 55.97282211541847, - "co2e": 2, + "co2e": 0.021217561706469563, "cost": 1.6084626580032653, "region": "us-east-1", "usesAverageCPUConstant": false @@ -604,7 +421,7 @@ "accountName": "gcp account 0", "serviceName": "computeEngine", "kilowattHours": 50.58370656686491, - "co2e": 2, + "co2e": 0.02428017915209516, "cost": 2.002632458514051, "region": "us-east1", "usesAverageCPUConstant": false @@ -614,7 +431,7 @@ "accountName": "azure account 2", "serviceName": "virtualMachines", "kilowattHours": 50.91095442793027, - "co2e": 4, + "co2e": 0.01145496474628431, "cost": 1.9447149842967881, "region": "UK South", "usesAverageCPUConstant": false @@ -622,14 +439,14 @@ ] }, { - "timestamp": "2020-10-25T00:00:00.000Z", + "timestamp": "2022-11-01T06:00:00.000Z", "serviceEstimates": [ { "cloudProvider": "AWS", "accountName": "aws account 2", "serviceName": "ebs", "kilowattHours": 0.8445236465308483, - "co2e": 3, + "co2e": 0.00032013273416680216, "cost": 1.8371062169192403, "region": "us-east-1", "usesAverageCPUConstant": false @@ -639,7 +456,7 @@ "accountName": "aws account 2", "serviceName": "s3", "kilowattHours": 0.0002648754924918617, - "co2e": 0, + "co2e": 1.0875999622109836e-7, "cost": 2.236483110571217, "region": "us-east-2", "usesAverageCPUConstant": false @@ -649,7 +466,7 @@ "accountName": "aws account 3", "serviceName": "ec2", "kilowattHours": 50.37629287099671, - "co2e": 0, + "co2e": 0.016229579145370397, "cost": 2.449362850757937, "region": "us-west-1", "usesAverageCPUConstant": false @@ -659,7 +476,7 @@ "accountName": "aws account 3", "serviceName": "rds", "kilowattHours": 50.52526081119707, - "co2e": 2, + "co2e": 0.016277571699760927, "cost": 2.302982452488826, "region": "us-west-2", "usesAverageCPUConstant": false @@ -669,7 +486,7 @@ "accountName": "aws account 3", "serviceName": "lambda", "kilowattHours": 50.357072062609866, - "co2e": 4, + "co2e": 0.01908880494970146, "cost": 2.3300950152526614, "region": "us-east-1", "usesAverageCPUConstant": false @@ -679,7 +496,7 @@ "accountName": "gcp account 0", "serviceName": "computeEngine", "kilowattHours": 53.70289639150512, - "co2e": 0, + "co2e": 0.02577739026792246, "cost": 1.8981217535028845, "region": "us-east1", "usesAverageCPUConstant": false @@ -689,7 +506,7 @@ "accountName": "azure account 4", "serviceName": "virtualMachines", "kilowattHours": 50.82389577797691, - "co2e": 3, + "co2e": 0.011435376550044804, "cost": 1.5108425566125716, "region": "UK South", "usesAverageCPUConstant": false @@ -697,14 +514,14 @@ ] }, { - "timestamp": "2020-09-25T00:00:00.000Z", + "timestamp": "2022-10-01T06:00:00.000Z", "serviceEstimates": [ { "cloudProvider": "AWS", "accountName": "aws account 0", "serviceName": "ebs", "kilowattHours": 0.33278898489743725, - "co2e": 3, + "co2e": 0.00012614998771608664, "cost": 2.201012388385645, "region": "us-east-1", "usesAverageCPUConstant": false @@ -714,7 +531,7 @@ "accountName": "aws account 1", "serviceName": "s3", "kilowattHours": 0.00098701927251199, - "co2e": 4, + "co2e": 4.052780094476032e-7, "cost": 1.645103657701107, "region": "us-east-2", "usesAverageCPUConstant": false @@ -724,7 +541,7 @@ "accountName": "aws account 4", "serviceName": "ec2", "kilowattHours": 50.904310297531644, - "co2e": 5, + "co2e": 0.016399688935624875, "cost": 2.235818514499144, "region": "us-west-1", "usesAverageCPUConstant": false @@ -734,7 +551,7 @@ "accountName": "aws account 3", "serviceName": "rds", "kilowattHours": 50.12852654254913, - "co2e": 2, + "co2e": 0.016149757010633425, "cost": 1.5786907055484218, "region": "us-west-2", "usesAverageCPUConstant": false @@ -744,7 +561,7 @@ "accountName": "aws account 2", "serviceName": "lambda", "kilowattHours": 59.54349975897656, - "co2e": 1, + "co2e": 0.022571094910135484, "cost": 2.324453671310117, "region": "us-east-1", "usesAverageCPUConstant": false @@ -754,7 +571,7 @@ "accountName": "gcp account 3", "serviceName": "computeEngine", "kilowattHours": 63.69530490695243, - "co2e": 4, + "co2e": 0.03057374635533717, "cost": 2.4104193295098923, "region": "us-east1", "usesAverageCPUConstant": false @@ -764,7 +581,7 @@ "accountName": "azure account 0", "serviceName": "virtualMachines", "kilowattHours": 65.60925208439582, - "co2e": 3, + "co2e": 0.014762081718989059, "cost": 2.4070137425368685, "region": "UK South", "usesAverageCPUConstant": false @@ -772,14 +589,14 @@ ] }, { - "timestamp": "2020-08-25T00:00:00.000Z", + "timestamp": "2022-09-01T06:00:00.000Z", "serviceEstimates": [ { "cloudProvider": "AWS", "accountName": "aws account 4", "serviceName": "ebs", "kilowattHours": 0.7421818551593697, - "co2e": 3, + "co2e": 0.00028133813365340714, "cost": 2.498136748374863, "region": "us-east-1", "usesAverageCPUConstant": false @@ -789,7 +606,7 @@ "accountName": "aws account 1", "serviceName": "s3", "kilowattHours": 0.0002180651158962681, - "co2e": 5, + "co2e": 8.953928110793486e-8, "cost": 1.7632736153406632, "region": "us-east-2", "usesAverageCPUConstant": false @@ -799,7 +616,7 @@ "accountName": "aws account 0", "serviceName": "ec2", "kilowattHours": 54.239336047864754, - "co2e": 3, + "co2e": 0.017474124176532442, "cost": 1.9661288302344184, "region": "us-west-1", "usesAverageCPUConstant": false @@ -809,7 +626,7 @@ "accountName": "aws account 0", "serviceName": "rds", "kilowattHours": 50.23120235816861, - "co2e": 2, + "co2e": 0.016182835770124106, "cost": 2.0142111162898986, "region": "us-west-2", "usesAverageCPUConstant": false @@ -819,7 +636,7 @@ "accountName": "aws account 3", "serviceName": "lambda", "kilowattHours": 50.86615400739505, - "co2e": 0, + "co2e": 0.019281782133429234, "cost": 2.164622206937599, "region": "us-east-1", "usesAverageCPUConstant": false @@ -829,7 +646,7 @@ "accountName": "gcp account 3", "serviceName": "computeEngine", "kilowattHours": 59.19683162717123, - "co2e": 5, + "co2e": 0.02841447918104219, "cost": 1.6906306697783389, "region": "us-east1", "usesAverageCPUConstant": false @@ -839,7 +656,7 @@ "accountName": "azure account 2", "serviceName": "virtualMachines", "kilowattHours": 50.27713613081028, - "co2e": 0, + "co2e": 0.011312355629432311, "cost": 1.7863670364270168, "region": "UK South", "usesAverageCPUConstant": false @@ -847,14 +664,14 @@ ] }, { - "timestamp": "2020-07-25T00:00:00.000Z", + "timestamp": "2022-08-01T06:00:00.000Z", "serviceEstimates": [ { "cloudProvider": "AWS", "accountName": "aws account 0", "serviceName": "ebs", "kilowattHours": 0.5648293345133781, - "co2e": 5, + "co2e": 0.00021410929100465172, "cost": 2.3304265345916653, "region": "us-east-1", "usesAverageCPUConstant": false @@ -864,7 +681,7 @@ "accountName": "aws account 4", "serviceName": "s3", "kilowattHours": 0.00005263625919629122, - "co2e": 2, + "co2e": 2.161286911607075e-8, "cost": 1.6038362843689178, "region": "us-east-2", "usesAverageCPUConstant": false @@ -874,7 +691,7 @@ "accountName": "aws account 0", "serviceName": "ec2", "kilowattHours": 64.1169430041115, - "co2e": 2, + "co2e": 0.02065636317680559, "cost": 1.8957903016901754, "region": "us-west-1", "usesAverageCPUConstant": false @@ -884,7 +701,7 @@ "accountName": "aws account 1", "serviceName": "rds", "kilowattHours": 50.69439982629539, - "co2e": 2, + "co2e": 0.016332062708838108, "cost": 2.3649041975938676, "region": "us-west-2", "usesAverageCPUConstant": false @@ -894,7 +711,7 @@ "accountName": "aws account 4", "serviceName": "lambda", "kilowattHours": 50.69873776324742, - "co2e": 4, + "co2e": 0.01921831982517644, "cost": 2.1212012771669846, "region": "us-east-1", "usesAverageCPUConstant": false @@ -904,7 +721,7 @@ "accountName": "gcp account 2", "serviceName": "computeEngine", "kilowattHours": 50.71620115791394, - "co2e": 5, + "co2e": 0.024343776555798693, "cost": 2.0052165772682793, "region": "us-east1", "usesAverageCPUConstant": false @@ -914,7 +731,7 @@ "accountName": "azure account 0", "serviceName": "virtualMachines", "kilowattHours": 50.146548832423804, - "co2e": 5, + "co2e": 0.011282973487295355, "cost": 2.4727258099898037, "region": "UK South", "usesAverageCPUConstant": false @@ -922,14 +739,14 @@ ] }, { - "timestamp": "2020-06-25T00:00:00.000Z", + "timestamp": "2022-07-01T06:00:00.000Z", "serviceEstimates": [ { "cloudProvider": "AWS", "accountName": "aws account 0", "serviceName": "ebs", "kilowattHours": 0.35269028620932774, - "co2e": 2, + "co2e": 0.00013369395410308364, "cost": 2.426536030208615, "region": "us-east-1", "usesAverageCPUConstant": false @@ -939,7 +756,7 @@ "accountName": "aws account 2", "serviceName": "s3", "kilowattHours": 0.00007772936246220752, - "co2e": 2, + "co2e": 3.1916298061882105e-8, "cost": 1.9926068310477163, "region": "us-east-2", "usesAverageCPUConstant": false @@ -949,7 +766,7 @@ "accountName": "aws account 4", "serviceName": "ec2", "kilowattHours": 50.00593572462313, - "co2e": 3, + "co2e": 0.016110262294594658, "cost": 1.9838511921282318, "region": "us-west-1", "usesAverageCPUConstant": false @@ -959,7 +776,7 @@ "accountName": "aws account 3", "serviceName": "rds", "kilowattHours": 50.803461347266776, - "co2e": 3, + "co2e": 0.016367198731864895, "cost": 2.229003808663734, "region": "us-west-2", "usesAverageCPUConstant": false @@ -969,7 +786,7 @@ "accountName": "aws account 0", "serviceName": "lambda", "kilowattHours": 52.62976125953012, - "co2e": 5, + "co2e": 0.019950310970888823, "cost": 1.6972395592555778, "region": "us-east-1", "usesAverageCPUConstant": false @@ -979,7 +796,7 @@ "accountName": "gcp account 4", "serviceName": "computeEngine", "kilowattHours": 64.63052755166217, - "co2e": 0, + "co2e": 0.03102265322479784, "cost": 2.30459191166866, "region": "us-east1", "usesAverageCPUConstant": false @@ -989,7 +806,7 @@ "accountName": "azure account 1", "serviceName": "virtualMachines", "kilowattHours": 53.31425378242701, - "co2e": 1, + "co2e": 0.011995707101046077, "cost": 1.6930870858502807, "region": "UK South", "usesAverageCPUConstant": false @@ -997,14 +814,14 @@ ] }, { - "timestamp": "2020-05-25T00:00:00.000Z", + "timestamp": "2022-06-01T06:00:00.000Z", "serviceEstimates": [ { "cloudProvider": "AWS", "accountName": "aws account 3", "serviceName": "ebs", "kilowattHours": 0.3793961762889928, - "co2e": 5, + "co2e": 0.00014381732914969223, "cost": 1.5660349090236223, "region": "us-east-1", "usesAverageCPUConstant": false @@ -1014,7 +831,7 @@ "accountName": "aws account 2", "serviceName": "s3", "kilowattHours": 0.0007881261941471649, - "co2e": 1, + "co2e": 3.2361092032637913e-7, "cost": 2.107104300712024, "region": "us-east-2", "usesAverageCPUConstant": false @@ -1024,7 +841,7 @@ "accountName": "aws account 3", "serviceName": "ec2", "kilowattHours": 50.4099085739953, - "co2e": 2, + "co2e": 0.016240409015558344, "cost": 1.7912671435326524, "region": "us-west-1", "usesAverageCPUConstant": false @@ -1034,7 +851,7 @@ "accountName": "aws account 1", "serviceName": "rds", "kilowattHours": 56.61135475563019, - "co2e": 3, + "co2e": 0.01823831032755711, "cost": 2.0547082075494254, "region": "us-west-2", "usesAverageCPUConstant": false @@ -1044,7 +861,7 @@ "accountName": "aws account 4", "serviceName": "lambda", "kilowattHours": 50.16319007556745, - "co2e": 1, + "co2e": 0.019015310298755278, "cost": 2.3442440999125886, "region": "us-east-1", "usesAverageCPUConstant": false @@ -1054,7 +871,7 @@ "accountName": "gcp account 1", "serviceName": "computeEngine", "kilowattHours": 50.2235650164992, - "co2e": 0, + "co2e": 0.02410731120791962, "cost": 1.6300863324887378, "region": "us-east1", "usesAverageCPUConstant": false @@ -1064,7 +881,7 @@ "accountName": "azure account 3", "serviceName": "virtualMachines", "kilowattHours": 50.27485053251475, - "co2e": 3, + "co2e": 0.011311841369815818, "cost": 1.7666414199697307, "region": "UK South", "usesAverageCPUConstant": false @@ -1072,14 +889,14 @@ ] }, { - "timestamp": "2020-04-25T00:00:00.000Z", + "timestamp": "2022-05-01T06:00:00.000Z", "serviceEstimates": [ { "cloudProvider": "AWS", "accountName": "aws account 3", "serviceName": "ebs", "kilowattHours": 0.0025963667867079376, - "co2e": 4, + "co2e": 9.842021614705911e-7, "cost": 2.110850189120251, "region": "us-east-1", "usesAverageCPUConstant": false @@ -1089,7 +906,7 @@ "accountName": "aws account 3", "serviceName": "s3", "kilowattHours": 0.00038988295545011175, - "co2e": 5, + "co2e": 1.600890605714595e-7, "cost": 1.750590801038299, "region": "us-east-2", "usesAverageCPUConstant": false @@ -1099,7 +916,7 @@ "accountName": "aws account 4", "serviceName": "ec2", "kilowattHours": 50.58419719553395, - "co2e": 3, + "co2e": 0.016296559057893588, "cost": 2.331961666410219, "region": "us-west-1", "usesAverageCPUConstant": false @@ -1109,7 +926,7 @@ "accountName": "aws account 1", "serviceName": "rds", "kilowattHours": 50.09219672002729, - "co2e": 1, + "co2e": 0.01613805274070103, "cost": 2.171231385841728, "region": "us-west-2", "usesAverageCPUConstant": false @@ -1119,7 +936,7 @@ "accountName": "aws account 0", "serviceName": "lambda", "kilowattHours": 50.34788323768369, - "co2e": 2, + "co2e": 0.01908532175102552, "cost": 2.178860527900543, "region": "us-east-1", "usesAverageCPUConstant": false @@ -1129,7 +946,7 @@ "accountName": "gcp account 2", "serviceName": "computeEngine", "kilowattHours": 50.876621782936894, - "co2e": 5, + "co2e": 0.02442077845580971, "cost": 2.008009566294815, "region": "us-east1", "usesAverageCPUConstant": false @@ -1139,7 +956,7 @@ "accountName": "azure account 2", "serviceName": "virtualMachines", "kilowattHours": 69.14280006058016, - "co2e": 1, + "co2e": 0.015557130013630535, "cost": 2.159049075087805, "region": "UK South", "usesAverageCPUConstant": false @@ -1147,14 +964,14 @@ ] }, { - "timestamp": "2020-03-25T00:00:00.000Z", + "timestamp": "2022-04-01T06:00:00.000Z", "serviceEstimates": [ { "cloudProvider": "AWS", "accountName": "aws account 3", "serviceName": "ebs", "kilowattHours": 0.847821319053617, - "co2e": 3, + "co2e": 0.00032138277959233556, "cost": 1.8756121359502171, "region": "us-east-1", "usesAverageCPUConstant": false @@ -1164,7 +981,7 @@ "accountName": "aws account 3", "serviceName": "s3", "kilowattHours": 0.000630093974723783, - "co2e": 3, + "co2e": 2.587216267733831e-7, "cost": 2.3698572941873914, "region": "us-east-2", "usesAverageCPUConstant": false @@ -1174,7 +991,7 @@ "accountName": "aws account 3", "serviceName": "ec2", "kilowattHours": 50.10307382046631, - "co2e": 4, + "co2e": 0.016141556983518168, "cost": 2.351022985532233, "region": "us-west-1", "usesAverageCPUConstant": false @@ -1184,7 +1001,7 @@ "accountName": "aws account 2", "serviceName": "rds", "kilowattHours": 50.22293041559674, - "co2e": 3, + "co2e": 0.016180170823201556, "cost": 1.5327957463127884, "region": "us-west-2", "usesAverageCPUConstant": false @@ -1194,7 +1011,7 @@ "accountName": "aws account 3", "serviceName": "lambda", "kilowattHours": 68.18586584927158, - "co2e": 2, + "co2e": 0.025847147981617528, "cost": 2.205957045144824, "region": "us-east-1", "usesAverageCPUConstant": false @@ -1204,7 +1021,7 @@ "accountName": "gcp account 4", "serviceName": "computeEngine", "kilowattHours": 68.47759811709409, - "co2e": 2, + "co2e": 0.032869247096205166, "cost": 2.026647361534485, "region": "us-east1", "usesAverageCPUConstant": false @@ -1214,7 +1031,7 @@ "accountName": "azure account 1", "serviceName": "virtualMachines", "kilowattHours": 50.5125618456214, - "co2e": 2, + "co2e": 0.011365326415264814, "cost": 1.9833785822656285, "region": "UK South", "usesAverageCPUConstant": false @@ -1222,14 +1039,14 @@ ] }, { - "timestamp": "2020-02-25T00:00:00.000Z", + "timestamp": "2022-03-01T07:00:00.000Z", "serviceEstimates": [ { "cloudProvider": "AWS", "accountName": "aws account 1", "serviceName": "ebs", "kilowattHours": 0.9119831149849784, - "co2e": 3, + "co2e": 0.00034570452741424076, "cost": 1.7466850895849164, "region": "us-east-1", "usesAverageCPUConstant": false @@ -1239,7 +1056,7 @@ "accountName": "aws account 1", "serviceName": "s3", "kilowattHours": 0.00010168764085273696, - "co2e": 2, + "co2e": 4.175375883526062e-8, "cost": 2.0929755865584747, "region": "us-east-2", "usesAverageCPUConstant": false @@ -1249,7 +1066,7 @@ "accountName": "aws account 2", "serviceName": "ec2", "kilowattHours": 50.13033413155082, - "co2e": 3, + "co2e": 0.01615033935615933, "cost": 2.030781896188273, "region": "us-west-1", "usesAverageCPUConstant": false @@ -1259,7 +1076,7 @@ "accountName": "aws account 3", "serviceName": "rds", "kilowattHours": 53.81264913939785, - "co2e": 5, + "co2e": 0.017336659735292387, "cost": 1.7863504430322306, "region": "us-west-2", "usesAverageCPUConstant": false @@ -1269,7 +1086,7 @@ "accountName": "aws account 4", "serviceName": "lambda", "kilowattHours": 63.45075892152083, - "co2e": 4, + "co2e": 0.02405221573362198, "cost": 1.8740007850011755, "region": "us-east-1", "usesAverageCPUConstant": false @@ -1279,7 +1096,7 @@ "accountName": "gcp account 4", "serviceName": "computeEngine", "kilowattHours": 50.77403726191187, - "co2e": 4, + "co2e": 0.0243715378857177, "cost": 1.7839515021969587, "region": "us-east1", "usesAverageCPUConstant": false @@ -1289,7 +1106,7 @@ "accountName": "azure account 4", "serviceName": "virtualMachines", "kilowattHours": 50.373127573933836, - "co2e": 2, + "co2e": 0.011333953704135112, "cost": 1.8199290662411896, "region": "UK South", "usesAverageCPUConstant": false @@ -1297,14 +1114,14 @@ ] }, { - "timestamp": "2020-01-25T00:00:00.000Z", + "timestamp": "2022-02-01T07:00:00.000Z", "serviceEstimates": [ { "cloudProvider": "AWS", "accountName": "aws account 1", "serviceName": "ebs", "kilowattHours": 0.8148394263939371, - "co2e": 3, + "co2e": 0.00030888036652372336, "cost": 1.9952062784974551, "region": "us-east-1", "usesAverageCPUConstant": false @@ -1314,7 +1131,7 @@ "accountName": "aws account 1", "serviceName": "s3", "kilowattHours": 0.00010100550904187954, - "co2e": 5, + "co2e": 4.147367005666808e-8, "cost": 2.479801470978538, "region": "us-east-2", "usesAverageCPUConstant": false @@ -1324,7 +1141,7 @@ "accountName": "aws account 1", "serviceName": "ec2", "kilowattHours": 50.933103987612505, - "co2e": 3, + "co2e": 0.016408965312377156, "cost": 2.014818576889102, "region": "us-west-1", "usesAverageCPUConstant": false @@ -1334,7 +1151,7 @@ "accountName": "aws account 2", "serviceName": "rds", "kilowattHours": 50.92736661108625, - "co2e": 2, + "co2e": 0.016407116918993825, "cost": 2.0552869714092585, "region": "us-west-2", "usesAverageCPUConstant": false @@ -1344,7 +1161,7 @@ "accountName": "aws account 2", "serviceName": "lambda", "kilowattHours": 70.47173348463286, - "co2e": 5, + "co2e": 0.026713649540286294, "cost": 1.8418849726705238, "region": "us-east-1", "usesAverageCPUConstant": false @@ -1354,7 +1171,7 @@ "accountName": "gcp account 3", "serviceName": "computeEngine", "kilowattHours": 57.666701915218034, - "co2e": 1, + "co2e": 0.02768001691930466, "cost": 1.611910606014586, "region": "us-east1", "usesAverageCPUConstant": false @@ -1364,7 +1181,7 @@ "accountName": "azure account 2", "serviceName": "virtualMachines", "kilowattHours": 50.773157853289746, - "co2e": 5, + "co2e": 0.011423960516990192, "cost": 2.2728625664822433, "region": "UK South", "usesAverageCPUConstant": false @@ -1384,7 +1201,7 @@ "resourceId": "i-0f12345678912b12I", "kilowattHourSavings": 116.513, "costSavings": 3.611, - "co2eSavings": 11.492 + "co2eSavings": 0.037536643671 }, { "cloudProvider": "AWS", @@ -1397,7 +1214,7 @@ "resourceId": "i-0f12345678912b12I", "kilowattHourSavings": 114.978, "costSavings": 13.506, - "co2eSavings": 11.984 + "co2eSavings": 0.047210886624 }, { "cloudProvider": "AWS", @@ -1410,7 +1227,7 @@ "resourceId": "i-0f12345678912b12I", "kilowattHourSavings": 18.419, "costSavings": 5.667, - "co2eSavings": 1.288 + "co2eSavings": 0.0069820719110000005 }, { "cloudProvider": "AWS", @@ -1423,7 +1240,7 @@ "resourceId": "i-0f12345678912b12I", "kilowattHourSavings": 11.195, "costSavings": 4.442, - "co2eSavings": 1.892 + "co2eSavings": 0.003606659565 }, { "cloudProvider": "AWS", @@ -1436,7 +1253,7 @@ "resourceId": "i-0f12345678912b12I", "kilowattHourSavings": 111.717, "costSavings": 5.788, - "co2eSavings": 11.972 + "co2eSavings": 0.035991530739 }, { "cloudProvider": "GCP", @@ -1449,7 +1266,7 @@ "resourceId": 6906976106869124000, "kilowattHourSavings": 12.081, "costSavings": 1.314, - "co2eSavings": 1.466 + "co2eSavings": 0.0009423179999999999 }, { "cloudProvider": "GCP", @@ -1462,7 +1279,7 @@ "resourceId": 4256745502855943000, "kilowattHourSavings": 18.742, "costSavings": 4.549, - "co2eSavings": 1.803 + "co2eSavings": 0.001461876 }, { "cloudProvider": "GCP", @@ -1475,7 +1292,7 @@ "resourceId": 9625521363699055000, "kilowattHourSavings": 16.989, "costSavings": 8.165, - "co2eSavings": 2.855 + "co2eSavings": 0.001325142 }, { "cloudProvider": "GCP", @@ -1488,7 +1305,7 @@ "resourceId": 9351508929180877000, "kilowattHourSavings": 19.946, "costSavings": 5.2, - "co2eSavings": 0.168 + "co2eSavings": 0.00957408 }, { "cloudProvider": "GCP", @@ -1501,7 +1318,7 @@ "resourceId": 7840914330904416000, "kilowattHourSavings": 18.782, "costSavings": 8.359, - "co2eSavings": 2.875 + "co2eSavings": 0.0047518460000000005 }, { "cloudProvider": "GCP", @@ -1514,12 +1331,12 @@ "resourceId": 8928403120086348000, "kilowattHourSavings": 116.483, "costSavings": 8.409, - "co2eSavings": 14.929 + "co2eSavings": 0.055911840000000004 }, { "cloudProvider": "GCP", - "accountId": "gcp account near zero", - "accountName": "gcp account near zero", + "accountId": "gcp account 4", + "accountName": "gcp account 4", "region": "us-east1", "recommendationType": "STOP_VM", "instanceName": "test-instance-9", @@ -1527,12 +1344,12 @@ "resourceId": 8928403120086348000, "kilowattHourSavings": 0.0001, "costSavings": 0.0002, - "co2eSavings": 0.0003 + "co2eSavings": 4.8000000000000006e-8 }, { "cloudProvider": "GCP", - "accountId": "gcp account at zero", - "accountName": "gcp account at zero", + "accountId": "gcp account 4", + "accountName": "gcp account 4", "region": "us-east1", "recommendationType": "STOP_VM", "instanceName": "test-instance-9", @@ -1544,8 +1361,8 @@ }, { "cloudProvider": "GCP", - "accountId": "gcp at 0.001", - "accountName": "gcp account at 0.001", + "accountId": "gcp account 4", + "accountName": "gcp account 4", "region": "us-east1", "recommendationType": "STOP_VM", "instanceName": "test-instance-9", @@ -1553,12 +1370,12 @@ "resourceId": 8928403120086348000, "kilowattHourSavings": 0.001, "costSavings": 0.001, - "co2eSavings": 0.001 + "co2eSavings": 4.800000000000001e-7 }, { "cloudProvider": "GCP", - "accountId": "gcp at 0.002", - "accountName": "gcp account at 0.002", + "accountId": "gcp account 4", + "accountName": "gcp account 4", "region": "us-east1", "recommendationType": "STOP_VM", "instanceName": "test-instance-9", @@ -1566,7 +1383,7 @@ "resourceId": 8928403120086348000, "kilowattHourSavings": 0.002, "costSavings": 0.002, - "co2eSavings": 0.002 + "co2eSavings": 9.600000000000001e-7 } ] -} \ No newline at end of file +} diff --git a/packages/integration-tests/package.json b/packages/integration-tests/package.json index cb740c6e4..4d96d24e1 100644 --- a/packages/integration-tests/package.json +++ b/packages/integration-tests/package.json @@ -5,7 +5,7 @@ "description": "Test repository to run integration tests", "scripts": { "start": "concurrently \"yarn start-client\" \"yarn start-api\"", - "start-client": "BROWSER=none REACT_APP_START_DATE=2020-01-01 REACT_APP_END_DATE=2021-03-01 REACT_APP_GROUP_BY=month REACT_APP_PAGE_LIMIT=1000 REACT_APP_BASE_URL=http://127.0.0.1:3000/api yarn workspace @cloud-carbon-footprint/client start", + "start-client": "BROWSER=none REACT_APP_TEST_MODE=true yarn workspace @cloud-carbon-footprint/client start", "start-api": "TEST_MODE=true CACHE_MODE=LOCAL ENABLE_CORS=false yarn workspace @cloud-carbon-footprint/api start", "headless": "testcafe 'chromium:headless' 'tests/*test.js'", "headed": "testcafe 'chrome'", diff --git a/packages/integration-tests/tests/app.test.js b/packages/integration-tests/tests/app.test.js index 46dacc705..7debabd4b 100644 --- a/packages/integration-tests/tests/app.test.js +++ b/packages/integration-tests/tests/app.test.js @@ -45,53 +45,49 @@ test('side drawer opens and closes when clicked', async (t) => { test('total metric tons is loaded correctly with different dropdown selections', async (t) => { await page.totalCo2Amount.with({ visibilityCheck: true }).exists //await core element before getting any of its text-specific versions //check initial value then check after each filter option - await t.expect(page.totalCo2Amount.withText('255').exists).ok() //the other problem element //todo: minimize dataset-specific selectors - await t.click(page.cloudProviderDropDown) - await t.click(page.awsDropdownItem) - await t.expect(page.totalCo2Amount.withText('74').exists).ok() - await t.click(page.accountsDropDown) - await t.click(page.accountsDropdownItem) - await t.expect(page.totalCo2Amount.withText('107').exists).ok() - await t.click(page.servicesDropDown) - await t.click(page.servicesDropdownItem) - await t.expect(page.totalCo2Amount.withText('93').exists).ok() + await t.expect(page.totalCo2Amount.withText('metric tons CO2e').exists).ok() //the other problem element }) test('carbon equivalency component displays each option when clicked', async (t) => { await t .click(page.flightsButton) - .expect(page.emissionsRecord.withText('315').exists) //todo: minimize dataset-specific selectors + .expect( + page.emissionsRecord.withText('direct one way flights from NYC to London') + .exists, + ) .ok() await t .click(page.phonesButton) - .expect(page.emissionsRecord.withText('31.0+ M').exists) + .expect(page.emissionsRecord.withText('smartphones charged').exists) .ok() await t .click(page.treesButton) - .expect(page.emissionsRecord.withText('4,216').exists) + .expect( + page.emissionsRecord.withText('tree seedlings grown for 10 years').exists, + ) .ok() }) test('emissions breakdown component displays each bar chart when selected', async (t) => { - // Maximize the window in orde for all DOM elements to be visible. + // Maximize the window in order for all DOM elements to be visible. // For some reason this stops this test failing, and can help with debugging. // In headless mode, issues have been noted that can be resolved by resizing window: https://github.com/DevExpress/testcafe/issues/6739 await t.maximizeWindow() //sort by account await t.click(page.dropDownSelector) await t.click(page.accountSelection) - await t.expect(page.selected.withText('aws account 3').exists).ok() //todo: minimize dataset-specific selectors + await t.expect(page.selected.exists).ok() //sort by service await t.click(page.dropDownSelector) await t.click(page.serviceSelection) - await t.expect(page.selected.withText('computeEngine').exists).ok() //todo: minimize dataset-specific selectors + await t.expect(page.selected.exists).ok() //sort by region await t.maximizeWindow() await t.click(page.dropDownSelector) await t.click(page.regionSelection) - await t.expect(page.selected.withText('us-east-1').exists).ok() //todo: minimize dataset-specific selectors + await t.expect(page.selected.exists).ok() }) test('line chart displays the y-axis data when legend is clicked', async (t) => { diff --git a/packages/integration-tests/tests/page-model.js b/packages/integration-tests/tests/page-model.js index f2cbbbdbd..25420d3ae 100644 --- a/packages/integration-tests/tests/page-model.js +++ b/packages/integration-tests/tests/page-model.js @@ -14,9 +14,9 @@ class Page { //--footprint page components here //main components - this.cloudProviders = Selector('span').withText('Cloud Providers: 3 of 3') - this.accounts = Selector('span').withText('Accounts: 15 of 15') //todo: minimize dataset-specific selectors - this.services = Selector('span').withText('Services: 8 of 8') + this.cloudProviders = Selector('span').withText('Cloud Providers') + this.accounts = Selector('span').withText('Accounts') + this.services = Selector('span').withText('Services') this.lineChart = Selector('#apexchartslineChart') this.carbonComparisonCard = Selector('#carbonComparisonCard') this.emissionsBreakdownContainer = Selector('#emissionsBreakdownContainer') @@ -68,11 +68,11 @@ class Page { //recommendations - main components this.recommendationsButton = Selector('a').withText('RECOMMENDATIONS') - this.regions = Selector('span').withText('Regions: 9 of 9') + this.regions = Selector('span').withText('Regions') this.recommendationTypes = Selector('span').withText( 'Recommendation Types: 8 of 8', ) - this.recAccounts = Selector('span').withText('Accounts: 15 of 15') //todo: minimize dataset-specific selectors + this.recAccounts = Selector('span').withText('Accounts') //forecast card components this.lastThirtyDayTotal = Selector( diff --git a/packages/integration-tests/tests/recommendations.test.js b/packages/integration-tests/tests/recommendations.test.js index f7a6be9a6..1f98efca6 100644 --- a/packages/integration-tests/tests/recommendations.test.js +++ b/packages/integration-tests/tests/recommendations.test.js @@ -32,13 +32,13 @@ test('filter components render with correct data when app loads', async (t) => { }) test('card components render with correct data when app loads', async (t) => { - //todo: refactor this test to inspect forecast component without error message - await t.expect(page.errorMessage.exists).ok() - // await t.expect(page.lastThirtyDayTotal.exists).ok() - // await t.expect(page.projectedThirtyDayTotal.exists).ok() - // await t.expect(page.forecastEquivalencyCard.exists).ok() - // await t.expect(page.treeSeedlingsGrown.textContent).eql('1,037') //todo: minimize dataset-specific selectors - // await t.expect(page.costSavingsPerMonth.textContent).eql('$69.01') + // commented out because the error message is not always present + // await t.expect(page.errorMessage.exists).ok() + await t.expect(page.lastThirtyDayTotal.exists).ok() + await t.expect(page.projectedThirtyDayTotal.exists).ok() + await t.expect(page.forecastEquivalencyCard.exists).ok() + await t.expect(page.treeSeedlingsGrown.exists).ok() + await t.expect(page.costSavingsPerMonth.exists).ok() }) test('table components render with correct data when app loads', async (t) => { @@ -48,43 +48,39 @@ test('table components render with correct data when app loads', async (t) => { test('toggle changes unit of measure', async (t) => { //check projected totals - //todo: refactor this test to inspect forecast component without error message - await t - .expect(page.errorMessage.textContent) - .eql( - 'In order to see a savings forecast that is relevant to you, please ensure you include data from the past 30 days', - ) + // commented out because the error message is not always present + // await t.expect(page.errorMessage.exists).ok() - // await t - // .expect(page.unitOfMeasureLastThirtyDayTotal.textContent) - // .eql('Metric Tons CO2e') - // await t - // .expect(page.unitOfMeasureProjectedThirtyDayTotal.textContent) - // .eql('Metric Tons CO2e') - // await t.expect(page.co2eSavingsLastThirtyDayTotal.textContent).eql('0') //todo: minimize dataset-specific selectors - // await t.expect(page.co2eSavingsProjectedThirtyDayTotal.textContent).eql('0') - // await t.expect(page.costSavingsLastThirtyDayTotal.textContent).eql('$0') - // await t.expect(page.costSavingsProjectedThirtyDayTotal.textContent).eql('$0') + await t + .expect(page.unitOfMeasureLastThirtyDayTotal.textContent) + .eql('Metric Tons CO2e') + await t + .expect(page.unitOfMeasureProjectedThirtyDayTotal.textContent) + .eql('Metric Tons CO2e') + await t.expect(page.co2eSavingsLastThirtyDayTotal.exists).ok() + await t.expect(page.co2eSavingsProjectedThirtyDayTotal.exists).ok() + await t.expect(page.costSavingsLastThirtyDayTotal.exists).ok() + await t.expect(page.costSavingsProjectedThirtyDayTotal.exists).ok() //check first cell await t .expect(page.tableSavingsColumn.textContent) .eql('Potential Carbon Savings (t)') - await t.expect(page.firstSavingsCell.textContent).eql('11.492') //todo: minimize dataset-specific selectors + await t.expect(page.firstSavingsCell.exists).ok() //click kilogram toggle await t.click(page.toggle, { isTrusted: true }) //recheck data in - kg instead of metric tons, so 1000 - // await t - // .expect(page.unitOfMeasureLastThirtyDayTotal.textContent) - // .eql('Kilograms CO2e') - // await t - // .expect(page.unitOfMeasureProjectedThirtyDayTotal.textContent) - // .eql('Kilograms CO2e') - // await t.expect(page.co2eSavingsLastThirtyDayTotal.textContent).eql('0') //todo: minimize dataset-specific selectors - // await t.expect(page.co2eSavingsProjectedThirtyDayTotal.textContent).eql('0') - // await t.expect(page.costSavingsLastThirtyDayTotal.textContent).eql('$0') - // await t.expect(page.costSavingsProjectedThirtyDayTotal.textContent).eql('$0') + await t + .expect(page.unitOfMeasureLastThirtyDayTotal.textContent) + .eql('Kilograms CO2e') + await t + .expect(page.unitOfMeasureProjectedThirtyDayTotal.textContent) + .eql('Kilograms CO2e') + await t.expect(page.co2eSavingsLastThirtyDayTotal.exists).ok() + await t.expect(page.co2eSavingsProjectedThirtyDayTotal.exists).ok() + await t.expect(page.costSavingsLastThirtyDayTotal.exists).ok() + await t.expect(page.costSavingsProjectedThirtyDayTotal.exists).ok() await t .expect(page.tableSavingsColumn.textContent) .eql('Potential Carbon Savings (kg)') - await t.expect(page.firstSavingsCell.textContent).eql('11492') //todo: minimize dataset-specific selectors + await t.expect(page.firstSavingsCell.exists).ok() }) diff --git a/scripts/create-client-mock-data.js b/scripts/create-client-mock-data.js index 6823c7c35..08813ae3e 100644 --- a/scripts/create-client-mock-data.js +++ b/scripts/create-client-mock-data.js @@ -4,6 +4,7 @@ const fs = require('fs') const path = require('path') +const moment = require('moment') async function main() { const data = fs.readFileSync( @@ -12,11 +13,25 @@ async function main() { ) const mockData = JSON.parse(data) - let updatedMonth = getPreviousMonth(new Date()); + let updatedMonth = moment().startOf('month') mockData.footprint.forEach((footprint) => { footprint.timestamp = updatedMonth.toISOString() updatedMonth = getPreviousMonth(updatedMonth) + + footprint.serviceEstimates.forEach((serviceEstimate) => { + const regionObj = mockData.emissions.find(o => o.region === serviceEstimate.region) + const { mtPerKwHour } = regionObj + const updatedC02e = serviceEstimate.kilowattHours * mtPerKwHour + serviceEstimate.co2e = updatedC02e + }) + }) + + mockData.recommendations.forEach((recommendation) => { + const regionObj = mockData.emissions.find(o => o.region === recommendation.region) + const { mtPerKwHour } = regionObj + const updatedC02e = recommendation.kilowattHourSavings * mtPerKwHour + recommendation.co2eSavings = updatedC02e }) fs.writeFileSync( @@ -29,33 +44,60 @@ async function main() { }, ) - Object.keys(mockData).forEach((key) => { - const pathSuffix = key === 'emissions' ? `regions/${key}` : key - - fs.writeFile( - path.resolve( - __dirname, - `../packages/client/stub-server/api/${pathSuffix}`, - ), - JSON.stringify(mockData[key]), - (err) => { - if (err) { - console.error(err) - return - } - }, - ) - }) + const mockDataPath = `../packages/api/mock-estimates.json` + await fileHandle(mockDataPath, mockData.footprint) +} + +const getPreviousMonth = (timestamp) => timestamp.subtract(1, 'months').startOf('month') + +// writes updates estimates to api mock data file in stream format +const fileHandle = async (mockDataFile, estimates) =>{ + let fh = null + try { + await fs.promises.writeFile(path.resolve(__dirname, mockDataFile), '', () =>{}) + fh = await fs.promises.open(path.resolve(__dirname, mockDataFile), 'r+') + await writeToFile(fs.promises, estimates, fh) + } catch (err) { + console.warn(`Setting estimates error: ${err.message}`) + } finally { + if (fh) { + await fh.close() + } + } } -function getPreviousMonth(timestamp) { - let prevMonth = timestamp - prevMonth.setDate(0) - prevMonth.setDate(1) - prevMonth.setUTCHours(0, 0, 0, 0) - return prevMonth +const writeToFile = async ( + writeStream, + mergedData, + fh, +) => { + const openBracket = '[' + '\n' + const closeBracket = '\n' + ']' + const commaSeparator = '\n' + ',' + '\n' + const dataWindowSize = 100 // this roughly means 100 days per loop + + async function writeIt(output) { + fh + ? await writeStream.appendFile(fh, output) + : await writeStream.write(output) + } + + await writeIt(openBracket) + for (let i = 0; i < mergedData.length; i += dataWindowSize) { + await writeIt( + mergedData + .slice(i, i + dataWindowSize) + .map((el) => JSON.stringify(el)) + .join(commaSeparator), + ) + if (i + dataWindowSize < mergedData.length) { + await writeIt(commaSeparator) + } + } + await writeIt(closeBracket) } + main().catch((error) => { console.error(error.stack) process.exit(1) From b7155df6d44a69e97203238af3b1f2abcdc411af Mon Sep 17 00:00:00 2001 From: Cam Casher Date: Tue, 18 Apr 2023 12:22:14 -0600 Subject: [PATCH 033/251] updates client templates --- .../default-app/packages/client/src/Config.ts | 27 +- .../packages/client/stub-server/mockData.json | 607 ++++++------------ 2 files changed, 229 insertions(+), 405 deletions(-) diff --git a/packages/create-app/templates/default-app/packages/client/src/Config.ts b/packages/create-app/templates/default-app/packages/client/src/Config.ts index 119839c3b..b2cacaaf9 100644 --- a/packages/create-app/templates/default-app/packages/client/src/Config.ts +++ b/packages/create-app/templates/default-app/packages/client/src/Config.ts @@ -19,13 +19,27 @@ export interface ClientConfig { START_DATE: string END_DATE: string DISABLE_CACHE: boolean + TEST_MODE: boolean } -const previousYearOfUsage = +let previousYearOfUsage = !!process.env.REACT_APP_PREVIOUS_YEAR_OF_USAGE && process.env.REACT_APP_PREVIOUS_YEAR_OF_USAGE !== 'false' +let groupBy = process.env.REACT_APP_GROUP_BY || 'day' +let pageLimit = process.env.REACT_APP_PAGE_LIMIT || '50000' +let baseUrl = process.env.REACT_APP_BASE_URL || '/api' +let endDate = process.env.REACT_APP_END_DATE +let minDateAge = process.env.REACT_APP_MINIMAL_DATE_AGE || '0' -const groupBy = process.env.REACT_APP_GROUP_BY || 'day' +// For local development / integration testing +if (process.env.REACT_APP_TEST_MODE === 'true') { + previousYearOfUsage = true + groupBy = 'month' + pageLimit = '1000' + baseUrl = 'http://127.0.0.1:3000/api' + endDate = null + minDateAge = '0' +} const appConfig: ClientConfig = { CURRENT_PROVIDERS: [ @@ -39,12 +53,13 @@ const appConfig: ClientConfig = { TYPE: process.env.REACT_APP_DATE_RANGE_TYPE || 'months', }, GROUP_BY: groupBy, - PAGE_LIMIT: process.env.REACT_APP_PAGE_LIMIT || '50000', - BASE_URL: process.env.REACT_APP_BASE_URL || '/api', - MINIMAL_DATE_AGE: process.env.REACT_APP_MINIMAL_DATE_AGE || '0', + PAGE_LIMIT: pageLimit, + BASE_URL: baseUrl, + MINIMAL_DATE_AGE: minDateAge, START_DATE: process.env.REACT_APP_START_DATE, - END_DATE: process.env.REACT_APP_END_DATE, + END_DATE: endDate, DISABLE_CACHE: process.env.REACT_APP_DISABLE_CACHE === 'true', + TEST_MODE: process.env.REACT_APP_TEST_MODE === 'true', } export default appConfig diff --git a/packages/create-app/templates/default-app/packages/client/stub-server/mockData.json b/packages/create-app/templates/default-app/packages/client/stub-server/mockData.json index b0bb8afe8..1ceac5f79 100644 --- a/packages/create-app/templates/default-app/packages/client/stub-server/mockData.json +++ b/packages/create-app/templates/default-app/packages/client/stub-server/mockData.json @@ -1,268 +1,77 @@ { "emissions": [ - { - "region": "us-east-1", - "mtPerKwHour": 0.0004545 - }, - { - "region": "us-east-2", - "mtPerKwHour": 0.000475105 - }, - { - "region": "us-west-1", - "mtPerKwHour": 0.000351533 - }, - { - "region": "us-west-2", - "mtPerKwHour": 0.000351533 - }, - { - "region": "us-gov-east-1", - "mtPerKwHour": 0.0004545 - }, - { - "region": "us-gov-west-1", - "mtPerKwHour": 0.000351533 - }, - { - "region": "af-south-1", - "mtPerKwHour": 0.000928 - }, - { - "region": "ap-east-1", - "mtPerKwHour": 0.00081 - }, - { - "region": "ap-south-1", - "mtPerKwHour": 0.000708 - }, - { - "region": "ap-northeast-3", - "mtPerKwHour": 0.000506 - }, - { - "region": "ap-northeast-2", - "mtPerKwHour": 0.0005 - }, - { - "region": "ap-southeast-1", - "mtPerKwHour": 0.0004085 - }, - { - "region": "ap-southeast-2", - "mtPerKwHour": 0.00079 - }, - { - "region": "ap-northeast-1", - "mtPerKwHour": 0.000506 - }, - { - "region": "ca-central-1", - "mtPerKwHour": 0.00013 - }, - { - "region": "cn-north-1", - "mtPerKwHour": 0.000555 - }, - { - "region": "cn-northwest-1", - "mtPerKwHour": 0.000555 - }, - { - "region": "eu-central-1", - "mtPerKwHour": 0.000338 - }, - { - "region": "eu-west-1", - "mtPerKwHour": 0.000316 - }, - { - "region": "eu-west-2", - "mtPerKwHour": 0.000228 - }, - { - "region": "eu-south-1", - "mtPerKwHour": 0.000233 - }, - { - "region": "eu-west-3", - "mtPerKwHour": 0.000052 - }, - { - "region": "eu-north-1", - "mtPerKwHour": 0.000008 - }, - { - "region": "me-south-1", - "mtPerKwHour": 0.000732 - }, - { - "region": "sa-east-1", - "mtPerKwHour": 0.000074 - }, - { - "region": "us-east2", - "mtPerKwHour": 0.000475105 - }, - { - "region": "us-central1", - "mtPerKwHour": 0.000540461 - }, - { - "region": "us-central2", - "mtPerKwHour": 0.000540461 - }, - { - "region": "us-east1", - "mtPerKwHour": 0.0004545 - }, - { - "region": "us-east4", - "mtPerKwHour": 0.0004545 - }, - { - "region": "us-west1", - "mtPerKwHour": 0.000351533 - }, - { - "region": "us-west2", - "mtPerKwHour": 0.000351533 - }, - { - "region": "us-west3", - "mtPerKwHour": 0.000351533 - }, - { - "region": "us-west4", - "mtPerKwHour": 0.000351533 - }, - { - "region": "asia-east1", - "mtPerKwHour": 0.000509 - }, - { - "region": "asia-east2", - "mtPerKwHour": 0.00081 - }, - { - "region": "asia-northeast1", - "mtPerKwHour": 0.000506 - }, - { - "region": "asia-northeast2", - "mtPerKwHour": 0.000506 - }, - { - "region": "asia-northeast3", - "mtPerKwHour": 0.0005 - }, - { - "region": "asia-south1", - "mtPerKwHour": 0.000708 - }, - { - "region": "asia-southeast1", - "mtPerKwHour": 0.0004085 - }, - { - "region": "asia-southeast2", - "mtPerKwHour": 0.000761 - }, - { - "region": "australia-southeast1", - "mtPerKwHour": 0.00079 - }, - { - "region": "europe-north1", - "mtPerKwHour": 0.000086 - }, - { - "region": "europe-west1", - "mtPerKwHour": 0.000167 - }, - { - "region": "europe-west2", - "mtPerKwHour": 0.000228 - }, - { - "region": "europe-west3", - "mtPerKwHour": 0.000338 - }, - { - "region": "europe-west4", - "mtPerKwHour": 0.00039 - }, - { - "region": "europe-west6", - "mtPerKwHour": 0.00001182 - }, - { - "region": "northamerica-northeast1", - "mtPerKwHour": 0.00013 - }, - { - "region": "southamerica-east1", - "mtPerKwHour": 0.000074 - }, - { - "region": "unknown", - "mtPerKwHour": 0.0004108907 - }, - { - "region": "AP East", - "mtPerKwHour": 0.00081 - }, - { - "region": "EU West", - "mtPerKwHour": 0.00039 - }, - { - "region": "IN Central", - "mtPerKwHour": 0.000708 - }, - { - "region": "UK South", - "mtPerKwHour": 0.000228 - }, - { - "region": "UK West", - "mtPerKwHour": 0.000228 - }, - { - "region": "US Central", - "mtPerKwHour": 0.000540461 - }, - { - "region": "US East", - "mtPerKwHour": 0.0004545 - }, - { - "region": "US South Central", - "mtPerKwHour": 0.000424877 - }, - { - "region": "US West", - "mtPerKwHour": 0.000351533 - }, - { - "region": "US West 2", - "mtPerKwHour": 0.000351533 - }, - { - "region": "Unknown", - "mtPerKwHour": 0.0004074 - } + { "region": "us-east-1", "mtPerKwHour": 0.000379069 }, + { "region": "us-east-2", "mtPerKwHour": 0.000410608 }, + { "region": "us-west-1", "mtPerKwHour": 0.000322167 }, + { "region": "us-west-2", "mtPerKwHour": 0.000322167 }, + { "region": "us-gov-east-1", "mtPerKwHour": 0.000379069 }, + { "region": "us-gov-west-1", "mtPerKwHour": 0.000322167 }, + { "region": "af-south-1", "mtPerKwHour": 0.0009006 }, + { "region": "ap-east-1", "mtPerKwHour": 0.00071 }, + { "region": "ap-south-1", "mtPerKwHour": 0.0007082 }, + { "region": "ap-northeast-3", "mtPerKwHour": 0.0004658 }, + { "region": "ap-northeast-2", "mtPerKwHour": 0.0004156 }, + { "region": "ap-southeast-1", "mtPerKwHour": 0.000408 }, + { "region": "ap-southeast-2", "mtPerKwHour": 0.00076 }, + { "region": "ap-northeast-1", "mtPerKwHour": 0.0004658 }, + { "region": "ca-central-1", "mtPerKwHour": 0.00012 }, + { "region": "cn-north-1", "mtPerKwHour": 0.0005374 }, + { "region": "cn-northwest-1", "mtPerKwHour": 0.0005374 }, + { "region": "eu-central-1", "mtPerKwHour": 0.000311 }, + { "region": "eu-west-1", "mtPerKwHour": 0.0002786 }, + { "region": "eu-west-2", "mtPerKwHour": 0.000225 }, + { "region": "eu-south-1", "mtPerKwHour": 0.0002134 }, + { "region": "eu-west-3", "mtPerKwHour": 0.0000511 }, + { "region": "eu-north-1", "mtPerKwHour": 0.0000088 }, + { "region": "me-south-1", "mtPerKwHour": 0.0005059 }, + { "region": "sa-east-1", "mtPerKwHour": 0.0000617 }, + { "region": "us-central1", "mtPerKwHour": 0.000454 }, + { "region": "us-central2", "mtPerKwHour": 0.000454 }, + { "region": "us-east1", "mtPerKwHour": 0.00048 }, + { "region": "us-east4", "mtPerKwHour": 0.000361 }, + { "region": "us-west1", "mtPerKwHour": 0.000078 }, + { "region": "us-west2", "mtPerKwHour": 0.000253 }, + { "region": "us-west3", "mtPerKwHour": 0.000533 }, + { "region": "us-west4", "mtPerKwHour": 0.000455 }, + { "region": "asia-east1", "mtPerKwHour": 0.00054 }, + { "region": "asia-east2", "mtPerKwHour": 0.000453 }, + { "region": "asia-northeast1", "mtPerKwHour": 0.000554 }, + { "region": "asia-northeast2", "mtPerKwHour": 0.000442 }, + { "region": "asia-northeast3", "mtPerKwHour": 0.000457 }, + { "region": "asia-south1", "mtPerKwHour": 0.000721 }, + { "region": "asia-southeast1", "mtPerKwHour": 0.000493 }, + { "region": "asia-southeast2", "mtPerKwHour": 0.000647 }, + { "region": "australia-southeast1", "mtPerKwHour": 0.000727 }, + { "region": "europe-north1", "mtPerKwHour": 0.000133 }, + { "region": "europe-west1", "mtPerKwHour": 0.000212 }, + { "region": "europe-west2", "mtPerKwHour": 0.000231 }, + { "region": "europe-west3", "mtPerKwHour": 0.000293 }, + { "region": "europe-west4", "mtPerKwHour": 0.00041 }, + { "region": "europe-west6", "mtPerKwHour": 0.000087 }, + { "region": "northamerica-northeast1", "mtPerKwHour": 0.000027 }, + { "region": "southamerica-east1", "mtPerKwHour": 0.000103 }, + { "region": "AP East", "mtPerKwHour": 0.00071 }, + { "region": "EU West", "mtPerKwHour": 0.0003284 }, + { "region": "IN Central", "mtPerKwHour": 0.0007082 }, + { "region": "UK South", "mtPerKwHour": 0.000225 }, + { "region": "UK West", "mtPerKwHour": 0.000225 }, + { "region": "US Central", "mtPerKwHour": 0.000426254 }, + { "region": "US East", "mtPerKwHour": 0.000379069 }, + { "region": "US South Central", "mtPerKwHour": 0.000373231 }, + { "region": "US West", "mtPerKwHour": 0.000322167 }, + { "region": "US West 2", "mtPerKwHour": 0.000322167 }, + { "region": "Unknown", "mtPerKwHour": 0.0003852304903666667 } ], "footprint": [ { - "timestamp": "2021-03-25T00:00:00.000Z", + "timestamp": "2023-04-01T06:00:00.000Z", "serviceEstimates": [ { "cloudProvider": "AWS", "accountName": "aws account 0", "serviceName": "ebs", "kilowattHours": 0.9099318685999922, - "co2e": 2, + "co2e": 0.00034492696349833045, "cost": 2.0611247334106126, "region": "us-east-1", "usesAverageCPUConstant": false @@ -272,7 +81,7 @@ "accountName": "aws account 0", "serviceName": "s3", "kilowattHours": 0.000986946262424086, - "co2e": 1, + "co2e": 4.0524803092142913e-7, "cost": 2.341122015683861, "region": "us-east-2", "usesAverageCPUConstant": false @@ -282,7 +91,7 @@ "accountName": "aws account 3", "serviceName": "ec2", "kilowattHours": 50.176579830111365, - "co2e": 3, + "co2e": 0.016165238194127487, "cost": 2.4154850519158613, "region": "us-west-1", "usesAverageCPUConstant": false @@ -292,7 +101,7 @@ "accountName": "aws account 3", "serviceName": "rds", "kilowattHours": 50.15554852182787, - "co2e": 4, + "co2e": 0.01615846260063172, "cost": 1.8825350541114, "region": "us-west-2", "usesAverageCPUConstant": false @@ -302,7 +111,7 @@ "accountName": "aws account 0", "serviceName": "lambda", "kilowattHours": 72.26453941514701, - "co2e": 2, + "co2e": 0.027393246691560364, "cost": 2.0084835420769656, "region": "us-east-1", "usesAverageCPUConstant": false @@ -312,7 +121,7 @@ "accountName": "gcp account 4", "serviceName": "computeEngine", "kilowattHours": 68.54797801901817, - "co2e": 4, + "co2e": 0.03290302944912873, "cost": 1.8688785461442674, "region": "us-east1", "usesAverageCPUConstant": false @@ -322,7 +131,7 @@ "accountName": "azure account 4", "serviceName": "virtualMachines", "kilowattHours": 50.02721869884774, - "co2e": 2, + "co2e": 0.011256124207240741, "cost": 2.2910467204304323, "region": "UK South", "usesAverageCPUConstant": false @@ -330,14 +139,14 @@ ] }, { - "timestamp": "2021-02-25T00:00:00.000Z", + "timestamp": "2023-03-01T07:00:00.000Z", "serviceEstimates": [ { "cloudProvider": "AWS", "accountName": "aws account 0", "serviceName": "ebs", "kilowattHours": 0.9281382786569483, - "co2e": 2, + "co2e": 0.00035182844915221075, "cost": 2.225771432143803, "region": "us-east-1", "usesAverageCPUConstant": false @@ -347,7 +156,7 @@ "accountName": "aws account 3", "serviceName": "s3", "kilowattHours": 0.00003270772530525257, - "co2e": 4, + "co2e": 1.3430053672139147e-8, "cost": 1.5028221506352941, "region": "us-east-2", "usesAverageCPUConstant": false @@ -357,7 +166,7 @@ "accountName": "aws account 3", "serviceName": "ec2", "kilowattHours": 50.558103056454314, - "co2e": 0, + "co2e": 0.016288152387388715, "cost": 2.440745140900739, "region": "us-west-1", "usesAverageCPUConstant": false @@ -367,7 +176,7 @@ "accountName": "aws account 3", "serviceName": "rds", "kilowattHours": 62.615457571422965, - "co2e": 0, + "co2e": 0.02017263411941262, "cost": 1.5955486508938537, "region": "us-west-2", "usesAverageCPUConstant": false @@ -377,7 +186,7 @@ "accountName": "aws account 0", "serviceName": "lambda", "kilowattHours": 50.903421738830794, - "co2e": 1, + "co2e": 0.01929590917511685, "cost": 2.238206567880293, "region": "us-east-1", "usesAverageCPUConstant": false @@ -387,7 +196,7 @@ "accountName": "gcp account 1", "serviceName": "computeEngine", "kilowattHours": 65.28200479098749, - "co2e": 3, + "co2e": 0.0050919963736970235, "cost": 1.6177172869839958, "region": "us-west1", "usesAverageCPUConstant": false @@ -397,7 +206,7 @@ "accountName": "azure account 1", "serviceName": "virtualMachines", "kilowattHours": 61.90994721222564, - "co2e": 2, + "co2e": 0.013929738122750768, "cost": 2.2081554041299234, "region": "UK South", "usesAverageCPUConstant": false @@ -405,14 +214,14 @@ ] }, { - "timestamp": "2021-01-25T00:00:00.000Z", + "timestamp": "2023-02-01T07:00:00.000Z", "serviceEstimates": [ { "cloudProvider": "AWS", "accountName": "aws account 1", "serviceName": "ebs", "kilowattHours": 0.9290466782034781, - "co2e": 0, + "co2e": 0.0003521727952599142, "cost": 1.9170203054445436, "region": "us-east-1", "usesAverageCPUConstant": false @@ -422,7 +231,7 @@ "accountName": "aws account 1", "serviceName": "s3", "kilowattHours": 0.00007414638904524828, - "co2e": 0, + "co2e": 3.0445100513091304e-8, "cost": 2.3496108515083787, "region": "us-east-2", "usesAverageCPUConstant": false @@ -432,7 +241,7 @@ "accountName": "aws account 3", "serviceName": "ec2", "kilowattHours": 50.74719344234976, - "co2e": 4, + "co2e": 0.016349071069741494, "cost": 1.8854738557659787, "region": "us-west-1", "usesAverageCPUConstant": false @@ -442,7 +251,7 @@ "accountName": "aws account 4", "serviceName": "rds", "kilowattHours": 62.70792733734538, - "co2e": 2, + "co2e": 0.02020242482649055, "cost": 1.9619661070136278, "region": "us-west-2", "usesAverageCPUConstant": false @@ -452,7 +261,7 @@ "accountName": "aws account 2", "serviceName": "lambda", "kilowattHours": 50.18018466997432, - "co2e": 2, + "co2e": 0.019021752422662495, "cost": 1.7366812626585235, "region": "us-east-1", "usesAverageCPUConstant": false @@ -462,9 +271,9 @@ "accountName": "gcp account 3", "serviceName": "computeEngine", "kilowattHours": 50.642678514609926, - "co2e": 4, + "co2e": 0.024308485687012764, "cost": 2.112304415112801, - "region": "us-east2", + "region": "us-east1", "usesAverageCPUConstant": false }, { @@ -472,7 +281,7 @@ "accountName": "azure account 1", "serviceName": "virtualMachines", "kilowattHours": 50.39790599515025, - "co2e": 5, + "co2e": 0.011339528848908806, "cost": 1.844015443986478, "region": "UK South", "usesAverageCPUConstant": false @@ -480,14 +289,14 @@ ] }, { - "timestamp": "2020-12-25T00:00:00.000Z", + "timestamp": "2023-01-01T07:00:00.000Z", "serviceEstimates": [ { "cloudProvider": "AWS", "accountName": "aws account 3", "serviceName": "ebs", "kilowattHours": 0.7903104123827822, - "co2e": 0, + "co2e": 0.0002995821777115289, "cost": 1.5941517912292598, "region": "us-east-1", "usesAverageCPUConstant": false @@ -497,7 +306,7 @@ "accountName": "aws account 3", "serviceName": "s3", "kilowattHours": 0.0008485340026475967, - "co2e": 5, + "co2e": 3.484148497591244e-7, "cost": 2.422188298668747, "region": "us-east-2", "usesAverageCPUConstant": false @@ -507,7 +316,7 @@ "accountName": "aws account 2", "serviceName": "ec2", "kilowattHours": 50.571988266018494, - "co2e": 0, + "co2e": 0.01629262574369838, "cost": 1.813087747561918, "region": "us-west-1", "usesAverageCPUConstant": false @@ -517,7 +326,7 @@ "accountName": "aws account 3", "serviceName": "rds", "kilowattHours": 50.54507810450079, - "co2e": 0, + "co2e": 0.016283956177692707, "cost": 2.3595674179586856, "region": "us-west-2", "usesAverageCPUConstant": false @@ -527,7 +336,7 @@ "accountName": "aws account 4", "serviceName": "lambda", "kilowattHours": 50.31390279587026, - "co2e": 2, + "co2e": 0.019072440818927745, "cost": 1.6209696239449094, "region": "us-east-1", "usesAverageCPUConstant": false @@ -537,7 +346,7 @@ "accountName": "gcp account 2", "serviceName": "computeEngine", "kilowattHours": 50.995343212272175, - "co2e": 5, + "co2e": 0.01290182183270486, "cost": 2.291652099168084, "region": "us-west2", "usesAverageCPUConstant": false @@ -547,7 +356,7 @@ "accountName": "azure account 1", "serviceName": "virtualMachines", "kilowattHours": 64.71563943290937, - "co2e": 4, + "co2e": 0.014561018872404607, "cost": 2.1519820863003964, "region": "UK South", "usesAverageCPUConstant": false @@ -555,14 +364,14 @@ ] }, { - "timestamp": "2020-11-25T00:00:00.000Z", + "timestamp": "2022-12-01T07:00:00.000Z", "serviceEstimates": [ { "cloudProvider": "AWS", "accountName": "aws account 3", "serviceName": "ebs", "kilowattHours": 0.616242116061845, - "co2e": 3, + "co2e": 0.00023359828269344752, "cost": 1.7611637388166252, "region": "us-east-1", "usesAverageCPUConstant": false @@ -572,7 +381,7 @@ "accountName": "aws account 2", "serviceName": "s3", "kilowattHours": 0.00009916949627876193, - "co2e": 0, + "co2e": 4.071978852802988e-8, "cost": 2.4426180887131026, "region": "us-east-2", "usesAverageCPUConstant": false @@ -582,7 +391,7 @@ "accountName": "aws account 2", "serviceName": "ec2", "kilowattHours": 50.149667680040125, - "co2e": 5, + "co2e": 0.016156567987475487, "cost": 2.2645178139778706, "region": "us-west-1", "usesAverageCPUConstant": false @@ -592,7 +401,7 @@ "accountName": "aws account 4", "serviceName": "rds", "kilowattHours": 50.15605062459718, - "co2e": 1, + "co2e": 0.0161586243615746, "cost": 2.345203244211236, "region": "us-west-2", "usesAverageCPUConstant": false @@ -602,7 +411,7 @@ "accountName": "aws account 2", "serviceName": "lambda", "kilowattHours": 55.97282211541847, - "co2e": 2, + "co2e": 0.021217561706469563, "cost": 1.6084626580032653, "region": "us-east-1", "usesAverageCPUConstant": false @@ -612,7 +421,7 @@ "accountName": "gcp account 0", "serviceName": "computeEngine", "kilowattHours": 50.58370656686491, - "co2e": 2, + "co2e": 0.02428017915209516, "cost": 2.002632458514051, "region": "us-east1", "usesAverageCPUConstant": false @@ -622,7 +431,7 @@ "accountName": "azure account 2", "serviceName": "virtualMachines", "kilowattHours": 50.91095442793027, - "co2e": 4, + "co2e": 0.01145496474628431, "cost": 1.9447149842967881, "region": "UK South", "usesAverageCPUConstant": false @@ -630,14 +439,14 @@ ] }, { - "timestamp": "2020-10-25T00:00:00.000Z", + "timestamp": "2022-11-01T06:00:00.000Z", "serviceEstimates": [ { "cloudProvider": "AWS", "accountName": "aws account 2", "serviceName": "ebs", "kilowattHours": 0.8445236465308483, - "co2e": 3, + "co2e": 0.00032013273416680216, "cost": 1.8371062169192403, "region": "us-east-1", "usesAverageCPUConstant": false @@ -647,7 +456,7 @@ "accountName": "aws account 2", "serviceName": "s3", "kilowattHours": 0.0002648754924918617, - "co2e": 0, + "co2e": 1.0875999622109836e-7, "cost": 2.236483110571217, "region": "us-east-2", "usesAverageCPUConstant": false @@ -657,7 +466,7 @@ "accountName": "aws account 3", "serviceName": "ec2", "kilowattHours": 50.37629287099671, - "co2e": 0, + "co2e": 0.016229579145370397, "cost": 2.449362850757937, "region": "us-west-1", "usesAverageCPUConstant": false @@ -667,7 +476,7 @@ "accountName": "aws account 3", "serviceName": "rds", "kilowattHours": 50.52526081119707, - "co2e": 2, + "co2e": 0.016277571699760927, "cost": 2.302982452488826, "region": "us-west-2", "usesAverageCPUConstant": false @@ -677,7 +486,7 @@ "accountName": "aws account 3", "serviceName": "lambda", "kilowattHours": 50.357072062609866, - "co2e": 4, + "co2e": 0.01908880494970146, "cost": 2.3300950152526614, "region": "us-east-1", "usesAverageCPUConstant": false @@ -687,7 +496,7 @@ "accountName": "gcp account 0", "serviceName": "computeEngine", "kilowattHours": 53.70289639150512, - "co2e": 0, + "co2e": 0.02577739026792246, "cost": 1.8981217535028845, "region": "us-east1", "usesAverageCPUConstant": false @@ -697,7 +506,7 @@ "accountName": "azure account 4", "serviceName": "virtualMachines", "kilowattHours": 50.82389577797691, - "co2e": 3, + "co2e": 0.011435376550044804, "cost": 1.5108425566125716, "region": "UK South", "usesAverageCPUConstant": false @@ -705,14 +514,14 @@ ] }, { - "timestamp": "2020-09-25T00:00:00.000Z", + "timestamp": "2022-10-01T06:00:00.000Z", "serviceEstimates": [ { "cloudProvider": "AWS", "accountName": "aws account 0", "serviceName": "ebs", "kilowattHours": 0.33278898489743725, - "co2e": 3, + "co2e": 0.00012614998771608664, "cost": 2.201012388385645, "region": "us-east-1", "usesAverageCPUConstant": false @@ -722,7 +531,7 @@ "accountName": "aws account 1", "serviceName": "s3", "kilowattHours": 0.00098701927251199, - "co2e": 4, + "co2e": 4.052780094476032e-7, "cost": 1.645103657701107, "region": "us-east-2", "usesAverageCPUConstant": false @@ -732,7 +541,7 @@ "accountName": "aws account 4", "serviceName": "ec2", "kilowattHours": 50.904310297531644, - "co2e": 5, + "co2e": 0.016399688935624875, "cost": 2.235818514499144, "region": "us-west-1", "usesAverageCPUConstant": false @@ -742,7 +551,7 @@ "accountName": "aws account 3", "serviceName": "rds", "kilowattHours": 50.12852654254913, - "co2e": 2, + "co2e": 0.016149757010633425, "cost": 1.5786907055484218, "region": "us-west-2", "usesAverageCPUConstant": false @@ -752,7 +561,7 @@ "accountName": "aws account 2", "serviceName": "lambda", "kilowattHours": 59.54349975897656, - "co2e": 1, + "co2e": 0.022571094910135484, "cost": 2.324453671310117, "region": "us-east-1", "usesAverageCPUConstant": false @@ -762,7 +571,7 @@ "accountName": "gcp account 3", "serviceName": "computeEngine", "kilowattHours": 63.69530490695243, - "co2e": 4, + "co2e": 0.03057374635533717, "cost": 2.4104193295098923, "region": "us-east1", "usesAverageCPUConstant": false @@ -772,7 +581,7 @@ "accountName": "azure account 0", "serviceName": "virtualMachines", "kilowattHours": 65.60925208439582, - "co2e": 3, + "co2e": 0.014762081718989059, "cost": 2.4070137425368685, "region": "UK South", "usesAverageCPUConstant": false @@ -780,14 +589,14 @@ ] }, { - "timestamp": "2020-08-25T00:00:00.000Z", + "timestamp": "2022-09-01T06:00:00.000Z", "serviceEstimates": [ { "cloudProvider": "AWS", "accountName": "aws account 4", "serviceName": "ebs", "kilowattHours": 0.7421818551593697, - "co2e": 3, + "co2e": 0.00028133813365340714, "cost": 2.498136748374863, "region": "us-east-1", "usesAverageCPUConstant": false @@ -797,7 +606,7 @@ "accountName": "aws account 1", "serviceName": "s3", "kilowattHours": 0.0002180651158962681, - "co2e": 5, + "co2e": 8.953928110793486e-8, "cost": 1.7632736153406632, "region": "us-east-2", "usesAverageCPUConstant": false @@ -807,7 +616,7 @@ "accountName": "aws account 0", "serviceName": "ec2", "kilowattHours": 54.239336047864754, - "co2e": 3, + "co2e": 0.017474124176532442, "cost": 1.9661288302344184, "region": "us-west-1", "usesAverageCPUConstant": false @@ -817,7 +626,7 @@ "accountName": "aws account 0", "serviceName": "rds", "kilowattHours": 50.23120235816861, - "co2e": 2, + "co2e": 0.016182835770124106, "cost": 2.0142111162898986, "region": "us-west-2", "usesAverageCPUConstant": false @@ -827,7 +636,7 @@ "accountName": "aws account 3", "serviceName": "lambda", "kilowattHours": 50.86615400739505, - "co2e": 0, + "co2e": 0.019281782133429234, "cost": 2.164622206937599, "region": "us-east-1", "usesAverageCPUConstant": false @@ -837,7 +646,7 @@ "accountName": "gcp account 3", "serviceName": "computeEngine", "kilowattHours": 59.19683162717123, - "co2e": 5, + "co2e": 0.02841447918104219, "cost": 1.6906306697783389, "region": "us-east1", "usesAverageCPUConstant": false @@ -847,7 +656,7 @@ "accountName": "azure account 2", "serviceName": "virtualMachines", "kilowattHours": 50.27713613081028, - "co2e": 0, + "co2e": 0.011312355629432311, "cost": 1.7863670364270168, "region": "UK South", "usesAverageCPUConstant": false @@ -855,14 +664,14 @@ ] }, { - "timestamp": "2020-07-25T00:00:00.000Z", + "timestamp": "2022-08-01T06:00:00.000Z", "serviceEstimates": [ { "cloudProvider": "AWS", "accountName": "aws account 0", "serviceName": "ebs", "kilowattHours": 0.5648293345133781, - "co2e": 5, + "co2e": 0.00021410929100465172, "cost": 2.3304265345916653, "region": "us-east-1", "usesAverageCPUConstant": false @@ -872,7 +681,7 @@ "accountName": "aws account 4", "serviceName": "s3", "kilowattHours": 0.00005263625919629122, - "co2e": 2, + "co2e": 2.161286911607075e-8, "cost": 1.6038362843689178, "region": "us-east-2", "usesAverageCPUConstant": false @@ -882,7 +691,7 @@ "accountName": "aws account 0", "serviceName": "ec2", "kilowattHours": 64.1169430041115, - "co2e": 2, + "co2e": 0.02065636317680559, "cost": 1.8957903016901754, "region": "us-west-1", "usesAverageCPUConstant": false @@ -892,7 +701,7 @@ "accountName": "aws account 1", "serviceName": "rds", "kilowattHours": 50.69439982629539, - "co2e": 2, + "co2e": 0.016332062708838108, "cost": 2.3649041975938676, "region": "us-west-2", "usesAverageCPUConstant": false @@ -902,7 +711,7 @@ "accountName": "aws account 4", "serviceName": "lambda", "kilowattHours": 50.69873776324742, - "co2e": 4, + "co2e": 0.01921831982517644, "cost": 2.1212012771669846, "region": "us-east-1", "usesAverageCPUConstant": false @@ -912,7 +721,7 @@ "accountName": "gcp account 2", "serviceName": "computeEngine", "kilowattHours": 50.71620115791394, - "co2e": 5, + "co2e": 0.024343776555798693, "cost": 2.0052165772682793, "region": "us-east1", "usesAverageCPUConstant": false @@ -922,7 +731,7 @@ "accountName": "azure account 0", "serviceName": "virtualMachines", "kilowattHours": 50.146548832423804, - "co2e": 5, + "co2e": 0.011282973487295355, "cost": 2.4727258099898037, "region": "UK South", "usesAverageCPUConstant": false @@ -930,14 +739,14 @@ ] }, { - "timestamp": "2020-06-25T00:00:00.000Z", + "timestamp": "2022-07-01T06:00:00.000Z", "serviceEstimates": [ { "cloudProvider": "AWS", "accountName": "aws account 0", "serviceName": "ebs", "kilowattHours": 0.35269028620932774, - "co2e": 2, + "co2e": 0.00013369395410308364, "cost": 2.426536030208615, "region": "us-east-1", "usesAverageCPUConstant": false @@ -947,7 +756,7 @@ "accountName": "aws account 2", "serviceName": "s3", "kilowattHours": 0.00007772936246220752, - "co2e": 2, + "co2e": 3.1916298061882105e-8, "cost": 1.9926068310477163, "region": "us-east-2", "usesAverageCPUConstant": false @@ -957,7 +766,7 @@ "accountName": "aws account 4", "serviceName": "ec2", "kilowattHours": 50.00593572462313, - "co2e": 3, + "co2e": 0.016110262294594658, "cost": 1.9838511921282318, "region": "us-west-1", "usesAverageCPUConstant": false @@ -967,7 +776,7 @@ "accountName": "aws account 3", "serviceName": "rds", "kilowattHours": 50.803461347266776, - "co2e": 3, + "co2e": 0.016367198731864895, "cost": 2.229003808663734, "region": "us-west-2", "usesAverageCPUConstant": false @@ -977,7 +786,7 @@ "accountName": "aws account 0", "serviceName": "lambda", "kilowattHours": 52.62976125953012, - "co2e": 5, + "co2e": 0.019950310970888823, "cost": 1.6972395592555778, "region": "us-east-1", "usesAverageCPUConstant": false @@ -987,7 +796,7 @@ "accountName": "gcp account 4", "serviceName": "computeEngine", "kilowattHours": 64.63052755166217, - "co2e": 0, + "co2e": 0.03102265322479784, "cost": 2.30459191166866, "region": "us-east1", "usesAverageCPUConstant": false @@ -997,7 +806,7 @@ "accountName": "azure account 1", "serviceName": "virtualMachines", "kilowattHours": 53.31425378242701, - "co2e": 1, + "co2e": 0.011995707101046077, "cost": 1.6930870858502807, "region": "UK South", "usesAverageCPUConstant": false @@ -1005,14 +814,14 @@ ] }, { - "timestamp": "2020-05-25T00:00:00.000Z", + "timestamp": "2022-06-01T06:00:00.000Z", "serviceEstimates": [ { "cloudProvider": "AWS", "accountName": "aws account 3", "serviceName": "ebs", "kilowattHours": 0.3793961762889928, - "co2e": 5, + "co2e": 0.00014381732914969223, "cost": 1.5660349090236223, "region": "us-east-1", "usesAverageCPUConstant": false @@ -1022,7 +831,7 @@ "accountName": "aws account 2", "serviceName": "s3", "kilowattHours": 0.0007881261941471649, - "co2e": 1, + "co2e": 3.2361092032637913e-7, "cost": 2.107104300712024, "region": "us-east-2", "usesAverageCPUConstant": false @@ -1032,7 +841,7 @@ "accountName": "aws account 3", "serviceName": "ec2", "kilowattHours": 50.4099085739953, - "co2e": 2, + "co2e": 0.016240409015558344, "cost": 1.7912671435326524, "region": "us-west-1", "usesAverageCPUConstant": false @@ -1042,7 +851,7 @@ "accountName": "aws account 1", "serviceName": "rds", "kilowattHours": 56.61135475563019, - "co2e": 3, + "co2e": 0.01823831032755711, "cost": 2.0547082075494254, "region": "us-west-2", "usesAverageCPUConstant": false @@ -1052,7 +861,7 @@ "accountName": "aws account 4", "serviceName": "lambda", "kilowattHours": 50.16319007556745, - "co2e": 1, + "co2e": 0.019015310298755278, "cost": 2.3442440999125886, "region": "us-east-1", "usesAverageCPUConstant": false @@ -1062,7 +871,7 @@ "accountName": "gcp account 1", "serviceName": "computeEngine", "kilowattHours": 50.2235650164992, - "co2e": 0, + "co2e": 0.02410731120791962, "cost": 1.6300863324887378, "region": "us-east1", "usesAverageCPUConstant": false @@ -1072,7 +881,7 @@ "accountName": "azure account 3", "serviceName": "virtualMachines", "kilowattHours": 50.27485053251475, - "co2e": 3, + "co2e": 0.011311841369815818, "cost": 1.7666414199697307, "region": "UK South", "usesAverageCPUConstant": false @@ -1080,14 +889,14 @@ ] }, { - "timestamp": "2020-04-25T00:00:00.000Z", + "timestamp": "2022-05-01T06:00:00.000Z", "serviceEstimates": [ { "cloudProvider": "AWS", "accountName": "aws account 3", "serviceName": "ebs", "kilowattHours": 0.0025963667867079376, - "co2e": 4, + "co2e": 9.842021614705911e-7, "cost": 2.110850189120251, "region": "us-east-1", "usesAverageCPUConstant": false @@ -1097,7 +906,7 @@ "accountName": "aws account 3", "serviceName": "s3", "kilowattHours": 0.00038988295545011175, - "co2e": 5, + "co2e": 1.600890605714595e-7, "cost": 1.750590801038299, "region": "us-east-2", "usesAverageCPUConstant": false @@ -1107,7 +916,7 @@ "accountName": "aws account 4", "serviceName": "ec2", "kilowattHours": 50.58419719553395, - "co2e": 3, + "co2e": 0.016296559057893588, "cost": 2.331961666410219, "region": "us-west-1", "usesAverageCPUConstant": false @@ -1117,7 +926,7 @@ "accountName": "aws account 1", "serviceName": "rds", "kilowattHours": 50.09219672002729, - "co2e": 1, + "co2e": 0.01613805274070103, "cost": 2.171231385841728, "region": "us-west-2", "usesAverageCPUConstant": false @@ -1127,7 +936,7 @@ "accountName": "aws account 0", "serviceName": "lambda", "kilowattHours": 50.34788323768369, - "co2e": 2, + "co2e": 0.01908532175102552, "cost": 2.178860527900543, "region": "us-east-1", "usesAverageCPUConstant": false @@ -1137,7 +946,7 @@ "accountName": "gcp account 2", "serviceName": "computeEngine", "kilowattHours": 50.876621782936894, - "co2e": 5, + "co2e": 0.02442077845580971, "cost": 2.008009566294815, "region": "us-east1", "usesAverageCPUConstant": false @@ -1147,7 +956,7 @@ "accountName": "azure account 2", "serviceName": "virtualMachines", "kilowattHours": 69.14280006058016, - "co2e": 1, + "co2e": 0.015557130013630535, "cost": 2.159049075087805, "region": "UK South", "usesAverageCPUConstant": false @@ -1155,14 +964,14 @@ ] }, { - "timestamp": "2020-03-25T00:00:00.000Z", + "timestamp": "2022-04-01T06:00:00.000Z", "serviceEstimates": [ { "cloudProvider": "AWS", "accountName": "aws account 3", "serviceName": "ebs", "kilowattHours": 0.847821319053617, - "co2e": 3, + "co2e": 0.00032138277959233556, "cost": 1.8756121359502171, "region": "us-east-1", "usesAverageCPUConstant": false @@ -1172,7 +981,7 @@ "accountName": "aws account 3", "serviceName": "s3", "kilowattHours": 0.000630093974723783, - "co2e": 3, + "co2e": 2.587216267733831e-7, "cost": 2.3698572941873914, "region": "us-east-2", "usesAverageCPUConstant": false @@ -1182,7 +991,7 @@ "accountName": "aws account 3", "serviceName": "ec2", "kilowattHours": 50.10307382046631, - "co2e": 4, + "co2e": 0.016141556983518168, "cost": 2.351022985532233, "region": "us-west-1", "usesAverageCPUConstant": false @@ -1192,7 +1001,7 @@ "accountName": "aws account 2", "serviceName": "rds", "kilowattHours": 50.22293041559674, - "co2e": 3, + "co2e": 0.016180170823201556, "cost": 1.5327957463127884, "region": "us-west-2", "usesAverageCPUConstant": false @@ -1202,7 +1011,7 @@ "accountName": "aws account 3", "serviceName": "lambda", "kilowattHours": 68.18586584927158, - "co2e": 2, + "co2e": 0.025847147981617528, "cost": 2.205957045144824, "region": "us-east-1", "usesAverageCPUConstant": false @@ -1212,7 +1021,7 @@ "accountName": "gcp account 4", "serviceName": "computeEngine", "kilowattHours": 68.47759811709409, - "co2e": 2, + "co2e": 0.032869247096205166, "cost": 2.026647361534485, "region": "us-east1", "usesAverageCPUConstant": false @@ -1222,7 +1031,7 @@ "accountName": "azure account 1", "serviceName": "virtualMachines", "kilowattHours": 50.5125618456214, - "co2e": 2, + "co2e": 0.011365326415264814, "cost": 1.9833785822656285, "region": "UK South", "usesAverageCPUConstant": false @@ -1230,14 +1039,14 @@ ] }, { - "timestamp": "2020-02-25T00:00:00.000Z", + "timestamp": "2022-03-01T07:00:00.000Z", "serviceEstimates": [ { "cloudProvider": "AWS", "accountName": "aws account 1", "serviceName": "ebs", "kilowattHours": 0.9119831149849784, - "co2e": 3, + "co2e": 0.00034570452741424076, "cost": 1.7466850895849164, "region": "us-east-1", "usesAverageCPUConstant": false @@ -1247,7 +1056,7 @@ "accountName": "aws account 1", "serviceName": "s3", "kilowattHours": 0.00010168764085273696, - "co2e": 2, + "co2e": 4.175375883526062e-8, "cost": 2.0929755865584747, "region": "us-east-2", "usesAverageCPUConstant": false @@ -1257,7 +1066,7 @@ "accountName": "aws account 2", "serviceName": "ec2", "kilowattHours": 50.13033413155082, - "co2e": 3, + "co2e": 0.01615033935615933, "cost": 2.030781896188273, "region": "us-west-1", "usesAverageCPUConstant": false @@ -1267,7 +1076,7 @@ "accountName": "aws account 3", "serviceName": "rds", "kilowattHours": 53.81264913939785, - "co2e": 5, + "co2e": 0.017336659735292387, "cost": 1.7863504430322306, "region": "us-west-2", "usesAverageCPUConstant": false @@ -1277,7 +1086,7 @@ "accountName": "aws account 4", "serviceName": "lambda", "kilowattHours": 63.45075892152083, - "co2e": 4, + "co2e": 0.02405221573362198, "cost": 1.8740007850011755, "region": "us-east-1", "usesAverageCPUConstant": false @@ -1287,7 +1096,7 @@ "accountName": "gcp account 4", "serviceName": "computeEngine", "kilowattHours": 50.77403726191187, - "co2e": 4, + "co2e": 0.0243715378857177, "cost": 1.7839515021969587, "region": "us-east1", "usesAverageCPUConstant": false @@ -1297,7 +1106,7 @@ "accountName": "azure account 4", "serviceName": "virtualMachines", "kilowattHours": 50.373127573933836, - "co2e": 2, + "co2e": 0.011333953704135112, "cost": 1.8199290662411896, "region": "UK South", "usesAverageCPUConstant": false @@ -1305,14 +1114,14 @@ ] }, { - "timestamp": "2020-01-25T00:00:00.000Z", + "timestamp": "2022-02-01T07:00:00.000Z", "serviceEstimates": [ { "cloudProvider": "AWS", "accountName": "aws account 1", "serviceName": "ebs", "kilowattHours": 0.8148394263939371, - "co2e": 3, + "co2e": 0.00030888036652372336, "cost": 1.9952062784974551, "region": "us-east-1", "usesAverageCPUConstant": false @@ -1322,7 +1131,7 @@ "accountName": "aws account 1", "serviceName": "s3", "kilowattHours": 0.00010100550904187954, - "co2e": 5, + "co2e": 4.147367005666808e-8, "cost": 2.479801470978538, "region": "us-east-2", "usesAverageCPUConstant": false @@ -1332,7 +1141,7 @@ "accountName": "aws account 1", "serviceName": "ec2", "kilowattHours": 50.933103987612505, - "co2e": 3, + "co2e": 0.016408965312377156, "cost": 2.014818576889102, "region": "us-west-1", "usesAverageCPUConstant": false @@ -1342,7 +1151,7 @@ "accountName": "aws account 2", "serviceName": "rds", "kilowattHours": 50.92736661108625, - "co2e": 2, + "co2e": 0.016407116918993825, "cost": 2.0552869714092585, "region": "us-west-2", "usesAverageCPUConstant": false @@ -1352,7 +1161,7 @@ "accountName": "aws account 2", "serviceName": "lambda", "kilowattHours": 70.47173348463286, - "co2e": 5, + "co2e": 0.026713649540286294, "cost": 1.8418849726705238, "region": "us-east-1", "usesAverageCPUConstant": false @@ -1362,7 +1171,7 @@ "accountName": "gcp account 3", "serviceName": "computeEngine", "kilowattHours": 57.666701915218034, - "co2e": 1, + "co2e": 0.02768001691930466, "cost": 1.611910606014586, "region": "us-east1", "usesAverageCPUConstant": false @@ -1372,7 +1181,7 @@ "accountName": "azure account 2", "serviceName": "virtualMachines", "kilowattHours": 50.773157853289746, - "co2e": 5, + "co2e": 0.011423960516990192, "cost": 2.2728625664822433, "region": "UK South", "usesAverageCPUConstant": false @@ -1392,7 +1201,7 @@ "resourceId": "i-0f12345678912b12I", "kilowattHourSavings": 116.513, "costSavings": 3.611, - "co2eSavings": 11.492 + "co2eSavings": 0.037536643671 }, { "cloudProvider": "AWS", @@ -1405,7 +1214,7 @@ "resourceId": "i-0f12345678912b12I", "kilowattHourSavings": 114.978, "costSavings": 13.506, - "co2eSavings": 11.984 + "co2eSavings": 0.047210886624 }, { "cloudProvider": "AWS", @@ -1418,7 +1227,7 @@ "resourceId": "i-0f12345678912b12I", "kilowattHourSavings": 18.419, "costSavings": 5.667, - "co2eSavings": 1.288 + "co2eSavings": 0.0069820719110000005 }, { "cloudProvider": "AWS", @@ -1431,7 +1240,7 @@ "resourceId": "i-0f12345678912b12I", "kilowattHourSavings": 11.195, "costSavings": 4.442, - "co2eSavings": 1.892 + "co2eSavings": 0.003606659565 }, { "cloudProvider": "AWS", @@ -1444,7 +1253,7 @@ "resourceId": "i-0f12345678912b12I", "kilowattHourSavings": 111.717, "costSavings": 5.788, - "co2eSavings": 11.972 + "co2eSavings": 0.035991530739 }, { "cloudProvider": "GCP", @@ -1457,7 +1266,7 @@ "resourceId": 6906976106869124000, "kilowattHourSavings": 12.081, "costSavings": 1.314, - "co2eSavings": 1.466 + "co2eSavings": 0.0009423179999999999 }, { "cloudProvider": "GCP", @@ -1470,7 +1279,7 @@ "resourceId": 4256745502855943000, "kilowattHourSavings": 18.742, "costSavings": 4.549, - "co2eSavings": 1.803 + "co2eSavings": 0.001461876 }, { "cloudProvider": "GCP", @@ -1483,20 +1292,20 @@ "resourceId": 9625521363699055000, "kilowattHourSavings": 16.989, "costSavings": 8.165, - "co2eSavings": 2.855 + "co2eSavings": 0.001325142 }, { "cloudProvider": "GCP", "accountId": "gcp account 3", "accountName": "gcp account 3", - "region": "us-east2", + "region": "us-east1", "recommendationType": "DELETE_ADDRESS", "instanceName": "test-instance-8", "recommendationDetail": "Save cost by performing a DELETE_ADDRESS for instance: test-instance-8.", "resourceId": 9351508929180877000, "kilowattHourSavings": 19.946, "costSavings": 5.2, - "co2eSavings": 0.168 + "co2eSavings": 0.00957408 }, { "cloudProvider": "GCP", @@ -1509,7 +1318,7 @@ "resourceId": 7840914330904416000, "kilowattHourSavings": 18.782, "costSavings": 8.359, - "co2eSavings": 2.875 + "co2eSavings": 0.0047518460000000005 }, { "cloudProvider": "GCP", @@ -1522,12 +1331,12 @@ "resourceId": 8928403120086348000, "kilowattHourSavings": 116.483, "costSavings": 8.409, - "co2eSavings": 14.929 + "co2eSavings": 0.055911840000000004 }, { "cloudProvider": "GCP", - "accountId": "gcp account near zero", - "accountName": "gcp account near zero", + "accountId": "gcp account 4", + "accountName": "gcp account 4", "region": "us-east1", "recommendationType": "STOP_VM", "instanceName": "test-instance-9", @@ -1535,25 +1344,25 @@ "resourceId": 8928403120086348000, "kilowattHourSavings": 0.0001, "costSavings": 0.0002, - "co2eSavings": 0.0003 + "co2eSavings": 4.8000000000000006e-8 }, { "cloudProvider": "GCP", - "accountId": "gcp account at zero", - "accountName": "gcp account at zero", + "accountId": "gcp account 4", + "accountName": "gcp account 4", "region": "us-east1", "recommendationType": "STOP_VM", "instanceName": "test-instance-9", "recommendationDetail": "Save cost by performing a STOP_VM for instance: test-instance-9.", "resourceId": 8928403120086348000, - "kilowattHourSavings": 0.0, - "costSavings": 0.0, - "co2eSavings": 0.0 + "kilowattHourSavings": 0, + "costSavings": 0, + "co2eSavings": 0 }, { "cloudProvider": "GCP", - "accountId": "gcp at 0.001", - "accountName": "gcp account at 0.001", + "accountId": "gcp account 4", + "accountName": "gcp account 4", "region": "us-east1", "recommendationType": "STOP_VM", "instanceName": "test-instance-9", @@ -1561,12 +1370,12 @@ "resourceId": 8928403120086348000, "kilowattHourSavings": 0.001, "costSavings": 0.001, - "co2eSavings": 0.001 + "co2eSavings": 4.800000000000001e-7 }, { "cloudProvider": "GCP", - "accountId": "gcp at 0.002", - "accountName": "gcp account at 0.002", + "accountId": "gcp account 4", + "accountName": "gcp account 4", "region": "us-east1", "recommendationType": "STOP_VM", "instanceName": "test-instance-9", @@ -1574,7 +1383,7 @@ "resourceId": 8928403120086348000, "kilowattHourSavings": 0.002, "costSavings": 0.002, - "co2eSavings": 0.002 + "co2eSavings": 9.600000000000001e-7 } ] } From bcb774299f0cbd5fb915efd6aac32097a6a94b02 Mon Sep 17 00:00:00 2001 From: Cam Casher Date: Tue, 18 Apr 2023 12:24:11 -0600 Subject: [PATCH 034/251] changest patch bumps --- .changeset/forty-lamps-fry.md | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 .changeset/forty-lamps-fry.md diff --git a/.changeset/forty-lamps-fry.md b/.changeset/forty-lamps-fry.md new file mode 100644 index 000000000..8cb499b56 --- /dev/null +++ b/.changeset/forty-lamps-fry.md @@ -0,0 +1,8 @@ +--- +'@cloud-carbon-footprint/api': patch +'@cloud-carbon-footprint/client': patch +'@cloud-carbon-footprint/create-app': patch +'@cloud-carbon-footprint/integration-tests': patch +--- + +fixes mock data and integration tests From 537f037a2fd0a8471258532a9935c8d9ab29f341 Mon Sep 17 00:00:00 2001 From: Arik Smith Date: Wed, 19 Apr 2023 19:47:19 -0400 Subject: [PATCH 035/251] updates service wrapper with client endpoints from google-cloud/compute package --- packages/gcp/src/lib/ServiceWrapper.ts | 137 +++++++++++++------------ 1 file changed, 72 insertions(+), 65 deletions(-) diff --git a/packages/gcp/src/lib/ServiceWrapper.ts b/packages/gcp/src/lib/ServiceWrapper.ts index c65d3b0b4..e95bac47c 100644 --- a/packages/gcp/src/lib/ServiceWrapper.ts +++ b/packages/gcp/src/lib/ServiceWrapper.ts @@ -7,38 +7,48 @@ import { protos as googleResource, } from '@google-cloud/resource-manager' import { RecommenderClient } from '@google-cloud/recommender' -import Compute, { protos as googleCompute } from '@google-cloud/compute' -import Project = googleResource.google.cloud.resourcemanager.v3.IProject +import { + protos as googleCompute, + InstancesClient, + DisksClient, + AddressesClient, + ImagesClient, + MachineTypesClient, +} from '@google-cloud/compute' import { GoogleAuthClient, Logger, wait } from '@cloud-carbon-footprint/common' -import { InstanceData } from '../__tests__/fixtures/googleapis.fixtures' import { ActiveProject, RecommenderRecommendations, } from './RecommendationsTypes' -import Instance = googleCompute.google.cloud.compute.v1.Instance -import MachineType = googleCompute.google.cloud.compute.v1.MachineType -import Disk = googleCompute.google.cloud.compute.v1.Disk -import Image = googleCompute.google.cloud.compute.v1.Image -import Address = googleCompute.google.cloud.compute.v1.Address -import InstancesScopedList = googleCompute.google.cloud.compute.v1.InstancesScopedList -import DisksScopedList = googleCompute.google.cloud.compute.v1.DisksScopedList -import AddressesScopedList = googleCompute.google.cloud.compute.v1.AddressesScopedList +// Interfaces +import Project = googleResource.google.cloud.resourcemanager.v3.IProject +import Instance = googleCompute.google.cloud.compute.v1.IInstance +import MachineType = googleCompute.google.cloud.compute.v1.IMachineType +import Disk = googleCompute.google.cloud.compute.v1.IDisk +import Image = googleCompute.google.cloud.compute.v1.IImage +import Address = googleCompute.google.cloud.compute.v1.IAddress +import InstancesScopedList = googleCompute.google.cloud.compute.v1.IInstancesScopedList +import DisksScopedList = googleCompute.google.cloud.compute.v1.IDisksScopedList +import AddressesScopedList = googleCompute.google.cloud.compute.v1.IAddressesScopedList const RETRY_AFTER = 10 -type Zone = [ - string, - InstancesScopedList | DisksScopedList | AddressesScopedList, -] +type IterableScopedList = AsyncIterable< + [string, InstancesScopedList | DisksScopedList | AddressesScopedList] +> export default class ServiceWrapper { private readonly serviceWrapperLogger: Logger private readonly noResultsOnPageMessage = 'NO_RESULTS_ON_PAGE' constructor( - private readonly googleProjectsClient: ProjectsClient, - private readonly googleAuthClient: GoogleAuthClient, - private readonly googleComputeClient: typeof Compute, - private readonly googleRecommenderClient: RecommenderClient, + private readonly projectsClient: ProjectsClient, + private readonly authClient: GoogleAuthClient, + private readonly instancesClient: InstancesClient, + private readonly disksClient: DisksClient, + private readonly addressesClient: AddressesClient, + private readonly imagesClient: ImagesClient, + private readonly machineTypesClient: MachineTypesClient, + private readonly recommenderClient: RecommenderClient, ) { this.serviceWrapperLogger = new Logger('GCP Service Wrapper') } @@ -62,7 +72,7 @@ export default class ServiceWrapper { } private async getProjects(): Promise { - const [projects] = await this.googleProjectsClient.searchProjects() + const [projects] = await this.projectsClient.searchProjects() return projects } @@ -72,23 +82,21 @@ export default class ServiceWrapper { try { const computeEngineRequest = { project: project.projectId, - auth: this.googleAuthClient, + auth: this.authClient, } - const instancesResult = - await this.googleComputeClient.instances.aggregatedList( - computeEngineRequest, - ) - const disksResult = await this.googleComputeClient.disks.aggregatedList( + const instancesResult = await this.instancesClient.aggregatedListAsync( + computeEngineRequest, + ) + const disksResult = await this.disksClient.aggregatedListAsync( + computeEngineRequest, + ) + const addressesResult = await this.addressesClient.aggregatedListAsync( computeEngineRequest, ) - const addressesResult = - await this.googleComputeClient.addresses.aggregatedList( - computeEngineRequest, - ) - const instanceZones = this.extractZones(instancesResult.data.items) - const diskZones = this.extractZones(disksResult.data.items) - const addressesZones = this.extractZones(addressesResult.data.items) + const instanceZones = await this.extractZones(instancesResult) + const diskZones = await this.extractZones(disksResult) + const addressesZones = await this.extractZones(addressesResult) return { id: project.projectId, @@ -108,13 +116,15 @@ export default class ServiceWrapper { } } - private extractZones(items: InstanceData): string[] { - if (!items) return [] - return Object.entries(items) - .filter((zone: Zone) => { - return zone[1].warning?.code !== this.noResultsOnPageMessage - }) - .map((zone) => zone[0].replace('zones/', '').replace('regions/', '')) + private async extractZones(results: IterableScopedList): Promise { + const items = [] + for await (const [zone, result] of results) { + if (result.warning?.code !== this.noResultsOnPageMessage) { + const formattedZone = zone.replace('zones/', '').replace('regions/', '') + items.push(formattedZone) + } + } + return items } async getRecommendationsForRecommenderIds( @@ -128,13 +138,12 @@ export default class ServiceWrapper { while (inProcess) { try { const [recommendations] = - await this.googleRecommenderClient.listRecommendations({ - parent: - this.googleRecommenderClient.projectLocationRecommenderPath( - projectId, - zone, - recommenderId, - ), + await this.recommenderClient.listRecommendations({ + parent: this.recommenderClient.projectLocationRecommenderPath( + projectId, + zone, + recommenderId, + ), }) inProcess = false recommendationByRecommenderIds.push({ @@ -164,12 +173,12 @@ export default class ServiceWrapper { project: projectId, zone: zone, instance: instanceId, - auth: this.googleAuthClient, + auth: this.authClient, } - const result = await this.googleComputeClient.instances.get( + const [instanceDetails] = await this.instancesClient.get( computeEngineRequest, ) - return result.data + return instanceDetails } async getMachineTypeDetails( @@ -181,12 +190,12 @@ export default class ServiceWrapper { project: projectId, zone: zone, machineType: machineType, - auth: this.googleAuthClient, + auth: this.authClient, } - const result = await this.googleComputeClient.machineTypes.get( + const [machineTypeDetails] = await this.machineTypesClient.get( machineTypeRequest, ) - return result.data + return machineTypeDetails } getStorageTypeFromDiskName(diskName: string): string { @@ -202,22 +211,20 @@ export default class ServiceWrapper { project: projectId, zone: zone, disk: diskId, - auth: this.googleAuthClient, + auth: this.authClient, } - const result = await this.googleComputeClient.disks.get(diskDetailsRequest) - return result.data + const [diskDetails] = await this.disksClient.get(diskDetailsRequest) + return diskDetails } async getImageDetails(projectId: string, imageId: string): Promise { - const ImageDetailsRequest = { + const imageDetailsRequest = { project: projectId, image: imageId, - auth: this.googleAuthClient, + auth: this.authClient, } - const result = await this.googleComputeClient.images.get( - ImageDetailsRequest, - ) - return result.data + const [imageDetails] = await this.imagesClient.get(imageDetailsRequest) + return imageDetails } async getAddressDetails( @@ -229,11 +236,11 @@ export default class ServiceWrapper { project: projectId, region: zone, address: addressId, - auth: this.googleAuthClient, + auth: this.authClient, } - const result = await this.googleComputeClient.addresses.get( + const [addressDetails] = await this.addressesClient.get( AddressDetailsRequest, ) - return result.data + return addressDetails } } From dc63c636dcc1a401227113a850432da721123198 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 15 Nov 2022 23:28:06 +0000 Subject: [PATCH 036/251] Bump loader-utils from 2.0.2 to 2.0.4 Bumps [loader-utils](https://github.com/webpack/loader-utils) from 2.0.2 to 2.0.4. - [Release notes](https://github.com/webpack/loader-utils/releases) - [Changelog](https://github.com/webpack/loader-utils/blob/v2.0.4/CHANGELOG.md) - [Commits](https://github.com/webpack/loader-utils/compare/v2.0.2...v2.0.4) --- updated-dependencies: - dependency-name: loader-utils dependency-type: indirect ... Signed-off-by: dependabot[bot] --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index 4481528f6..c158810ec 100644 --- a/yarn.lock +++ b/yarn.lock @@ -15222,13 +15222,13 @@ __metadata: linkType: hard "loader-utils@npm:^2.0.0": - version: 2.0.2 - resolution: "loader-utils@npm:2.0.2" + version: 2.0.4 + resolution: "loader-utils@npm:2.0.4" dependencies: big.js: ^5.2.2 emojis-list: ^3.0.0 json5: ^2.1.2 - checksum: 9078d1ed47cadc57f4c6ddbdb2add324ee7da544cea41de3b7f1128e8108fcd41cd3443a85b7ee8d7d8ac439148aa221922774efe4cf87506d4fb054d5889303 + checksum: a5281f5fff1eaa310ad5e1164095689443630f3411e927f95031ab4fb83b4a98f388185bb1fe949e8ab8d4247004336a625e9255c22122b815bb9a4c5d8fc3b7 languageName: node linkType: hard From 7952c870446b78b9d96ac562daa7e765d99fc74d Mon Sep 17 00:00:00 2001 From: Arik Smith Date: Thu, 20 Apr 2023 19:55:53 -0400 Subject: [PATCH 037/251] removes use of googleapis clients from main account --- packages/gcp/src/application/GCPAccount.ts | 33 ++++++++++++++-------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/packages/gcp/src/application/GCPAccount.ts b/packages/gcp/src/application/GCPAccount.ts index 7c864539f..e7e66ef59 100644 --- a/packages/gcp/src/application/GCPAccount.ts +++ b/packages/gcp/src/application/GCPAccount.ts @@ -7,9 +7,13 @@ import { ClientOptions } from 'google-gax' import { BigQuery } from '@google-cloud/bigquery' import { ProjectsClient } from '@google-cloud/resource-manager' import { RecommenderClient } from '@google-cloud/recommender' -import { APIEndpoint } from 'googleapis-common' -import { compute as googleCompute } from 'googleapis/build/src/apis/compute' -import { auth as googleAuth } from 'googleapis/build/src/apis/iam' +import { + InstancesClient, + DisksClient, + AddressesClient, + ImagesClient, + MachineTypesClient, +} from '@google-cloud/compute' import { ICloudService, Region, @@ -30,6 +34,7 @@ import { LookupTableOutput, GroupBy, } from '@cloud-carbon-footprint/common' +import { GoogleAuth } from 'google-auth-library' import ServiceWrapper from '../lib/ServiceWrapper' import { BillingExportTable, ComputeEngine, Recommendations } from '../lib' import { GCP_CLOUD_CONSTANTS, getGCPEmissionsFactors } from '../domain' @@ -126,21 +131,27 @@ export default class GCPAccount extends CloudProviderAccount { } async getDataForRecommendations(): Promise { - const googleAuthClient: GoogleAuthClient = await googleAuth.getClient({ + const auth = new GoogleAuth({ scopes: ['https://www.googleapis.com/auth/cloud-platform'], }) - const googleComputeClient: APIEndpoint = googleCompute('v1') + const googleAuthClient: GoogleAuthClient = await auth.getClient() + + const serviceWrapper = new ServiceWrapper( + new ProjectsClient(), + googleAuthClient, + new InstancesClient(), + new DisksClient(), + new AddressesClient(), + new ImagesClient(), + new MachineTypesClient(), + new RecommenderClient(), + ) const recommendations = new Recommendations( new ComputeEstimator(), new StorageEstimator(GCP_CLOUD_CONSTANTS.HDDCOEFFICIENT), new StorageEstimator(GCP_CLOUD_CONSTANTS.SSDCOEFFICIENT), - new ServiceWrapper( - new ProjectsClient(), - googleAuthClient, - googleComputeClient, - new RecommenderClient(), - ), + serviceWrapper, ) return await recommendations.getRecommendations() From e089007aa79209b6d860d1f4d2cddef838063410 Mon Sep 17 00:00:00 2001 From: Arik Smith Date: Fri, 21 Apr 2023 17:20:03 -0400 Subject: [PATCH 038/251] updates types and fixtures for service wrapper test based on compute engine client results --- packages/gcp/src/__tests__/GCPAccount.test.ts | 4 +- .../gcp/src/__tests__/ServiceWrapper.test.ts | 67 +++---- .../__tests__/fixtures/googleapis.fixtures.ts | 186 ++++++++---------- packages/gcp/src/lib/BillingExportRow.ts | 2 +- packages/gcp/src/lib/Recommendations.ts | 4 +- packages/gcp/src/lib/ServiceWrapper.ts | 8 +- packages/gcp/src/lib/ServiceWrapperTypes.ts | 11 ++ 7 files changed, 129 insertions(+), 153 deletions(-) create mode 100644 packages/gcp/src/lib/ServiceWrapperTypes.ts diff --git a/packages/gcp/src/__tests__/GCPAccount.test.ts b/packages/gcp/src/__tests__/GCPAccount.test.ts index ade7f0483..3f71c00f6 100644 --- a/packages/gcp/src/__tests__/GCPAccount.test.ts +++ b/packages/gcp/src/__tests__/GCPAccount.test.ts @@ -2,7 +2,7 @@ * © 2021 Thoughtworks, Inc. */ -import { auth as googleAuth } from 'googleapis/build/src/apis/iam' +import { GoogleAuth } from 'google-auth-library' import { ComputeEngine, Recommendations } from '../lib' @@ -174,7 +174,7 @@ describe('GCPAccount', () => { }, ] - const getClientSpy = jest.spyOn(googleAuth, 'getClient') + const getClientSpy = jest.spyOn(GoogleAuth.prototype, 'getClient') ;(getClientSpy as jest.Mock).mockResolvedValue(jest.fn()) diff --git a/packages/gcp/src/__tests__/ServiceWrapper.test.ts b/packages/gcp/src/__tests__/ServiceWrapper.test.ts index 7864be3da..03408dd2c 100644 --- a/packages/gcp/src/__tests__/ServiceWrapper.test.ts +++ b/packages/gcp/src/__tests__/ServiceWrapper.test.ts @@ -3,7 +3,14 @@ */ import { ProjectsClient } from '@google-cloud/resource-manager' -import Compute, { protos as googleCompute } from '@google-cloud/compute' +import { + AddressesClient, + DisksClient, + ImagesClient, + InstancesClient, + MachineTypesClient, + protos as googleCompute, +} from '@google-cloud/compute' import { GoogleAuth } from 'google-auth-library' import { RecommenderClient } from '@google-cloud/recommender' import { GoogleAuthClient, wait } from '@cloud-carbon-footprint/common' @@ -20,8 +27,8 @@ import { } from './fixtures/googleapis.fixtures' import { mockedProjects } from './fixtures/resourceManager.fixtures' import { mockStopVMRecommendationsResults } from './fixtures/recommender.fixtures' -import Instance = googleCompute.google.cloud.compute.v1.Instance -import MachineType = googleCompute.google.cloud.compute.v1.MachineType +import Instance = googleCompute.google.cloud.compute.v1.IInstance +import MachineType = googleCompute.google.cloud.compute.v1.IMachineType import ServiceWrapper from '../lib/ServiceWrapper' import { ActiveProject, @@ -64,55 +71,38 @@ describe('GCP Service Wrapper', () => { ;(getClientSpy as jest.Mock).mockResolvedValue(jest.fn()) const googleAuthClient: GoogleAuthClient = await auth.getClient() - const googleComputeClient = Compute.v1 serviceWrapper = new ServiceWrapper( new ProjectsClient(), googleAuthClient, - googleComputeClient, + new InstancesClient(), + new DisksClient(), + new AddressesClient(), + new ImagesClient(), + new MachineTypesClient(), new RecommenderClient(), ) setupSpy( - googleComputeClient.InstancesClient.prototype, + InstancesClient.prototype, 'aggregatedListAsync', mockedInstanceResultItems, ) setupSpy( - googleComputeClient.DisksClient.prototype, + DisksClient.prototype, 'aggregatedListAsync', mockedDisksResultItems, ) + setupSpy(DisksClient.prototype, 'get', mockedDisksGetSSDDetails) setupSpy( - googleComputeClient.DisksClient.prototype, - 'get', - mockedDisksGetSSDDetails, - ) - setupSpy( - googleComputeClient.AddressesClient.prototype, + AddressesClient.prototype, 'aggregatedListAsync', mockedAddressesResultItems, ) - setupSpy( - googleComputeClient.MachineTypesClient.prototype, - 'get', - mockedMachineTypesGetItems, - ) - setupSpy( - googleComputeClient.InstancesClient.prototype, - 'get', - mockedInstanceGetItems, - ) - setupSpy( - googleComputeClient.ImagesClient.prototype, - 'get', - mockedImageGetDetails, - ) - setupSpy( - googleComputeClient.AddressesClient.prototype, - 'get', - mockedAddressGetDetails, - ) + setupSpy(MachineTypesClient.prototype, 'get', mockedMachineTypesGetItems) + setupSpy(InstancesClient.prototype, 'get', mockedInstanceGetItems) + setupSpy(ImagesClient.prototype, 'get', mockedImageGetDetails) + setupSpy(AddressesClient.prototype, 'get', mockedAddressGetDetails) }) it('gets active projects', async () => { @@ -253,7 +243,6 @@ describe('GCP Service Wrapper', () => { describe('error handling', () => { let serviceWrapper: ServiceWrapper - const googleComputeClient = Compute.v1 beforeEach(async () => { const auth = new GoogleAuth({ @@ -269,15 +258,19 @@ describe('GCP Service Wrapper', () => { serviceWrapper = new ServiceWrapper( new ProjectsClient(), googleAuthClient, - googleComputeClient, + new InstancesClient(), + new DisksClient(), + new AddressesClient(), + new ImagesClient(), + new MachineTypesClient(), new RecommenderClient(), ) }) it('fails to get active zones for project', async () => { setupSpyWithRejectedValue( - googleComputeClient.InstancesClient, - 'aggregatedList', + InstancesClient.prototype, + 'aggregatedListAsync', 'error', ) const activeProjectsAndZones: ActiveProject[] = diff --git a/packages/gcp/src/__tests__/fixtures/googleapis.fixtures.ts b/packages/gcp/src/__tests__/fixtures/googleapis.fixtures.ts index 154ff0d3b..684df7b80 100644 --- a/packages/gcp/src/__tests__/fixtures/googleapis.fixtures.ts +++ b/packages/gcp/src/__tests__/fixtures/googleapis.fixtures.ts @@ -2,131 +2,109 @@ * © 2021 Thoughtworks, Inc. */ -import { protos as googleCompute } from '@google-cloud/compute' -import Instance = googleCompute.google.cloud.compute.v1.Instance -import MachineType = googleCompute.google.cloud.compute.v1.MachineType -import Disk = googleCompute.google.cloud.compute.v1.Disk -import Image = googleCompute.google.cloud.compute.v1.Image -import Address = googleCompute.google.cloud.compute.v1.Address -import InstanceAggregatedList = google.cloud.compute.v1.InstanceAggregatedList -import DiskAggregatedList = google.cloud.compute.v1.DiskAggregatedList -import AddressAggregatedList = google.cloud.compute.v1.AddressAggregatedList +import { ScopedListResult } from '../../lib/ServiceWrapperTypes' import { google } from '@google-cloud/compute/build/protos/protos' +import IDisk = google.cloud.compute.v1.IDisk +import IInstance = google.cloud.compute.v1.IInstance +import IMachineType = google.cloud.compute.v1.IMachineType +import IImage = google.cloud.compute.v1.IImage +import IAddress = google.cloud.compute.v1.IAddress -export type InstanceData = { - data: Partial +interface IterableMockResponse { + next(): Promise> } -type MachineTypeData = { - data: Partial -} - -type ImageDetails = { - data: Partial -} - -type DiskData = { - data: Partial -} - -type AddressDetails = { - data: Partial
-} - -type InstanceAggregatedListData = { - data: Partial -} - -type DiskAggregatedListData = { - data: Partial +const mockAsyncIterator = { + // * = fancy generator function (https://javascript.info/generators) + async *[Symbol.asyncIterator](mockResponse: ScopedListResult[]) { + for (const response of mockResponse) yield response + }, } -type AddressAggregatedListData = { - data: Partial +export const mockIterableResponse = ( + mockResponse: ScopedListResult[], +): IterableMockResponse => { + return mockAsyncIterator[Symbol.asyncIterator](mockResponse) } -export const mockedInstanceResultItems: InstanceAggregatedListData = { - data: { - items: { - 'zones/us-west1-a': { - instances: [{ id: 'test-instance' }], - }, - 'zones/us-east1-a': { - instances: [{ id: 'test-instance-1' }], - }, - 'zones/us-west1-b': { warning: { code: 'NO_RESULTS_ON_PAGE' } }, +export const mockedInstanceResultItems = mockIterableResponse([ + [ + 'zones/us-west1-a', + { + instances: [{ id: 'test-instance' }], }, - }, -} - -export const mockedInstanceRegionsResultItems: InstanceAggregatedListData = { - data: { - items: { - 'regions/us-west1': { - instances: [{ id: 'test-instance' }], - }, + ], + [ + 'zones/us-east1-a', + { + instances: [{ id: 'test-instance-1' }], }, - }, -} - -export const mockedInstanceGlobalResultItems: InstanceAggregatedListData = { - data: { - items: { - global: { - instances: [{ id: 'test-instance-global' }], - }, + ], + ['zones/us-west1-b', { warning: { code: 'NO_RESULTS_ON_PAGE' } }], +]) + +export const mockedInstanceRegionsResultItems = mockIterableResponse([ + [ + 'regions/us-west1', + { + instances: [{ id: 'test-instance' }], }, - }, -} + ], +]) + +export const mockedInstanceGlobalResultItems = mockIterableResponse([ + [ + 'global', + { + instances: [{ id: 'test-instance-global' }], + }, + ], +]) -export const mockedAddressesResultItems: AddressAggregatedListData = { - data: { items: {} }, -} +export const mockedAddressesResultItems = mockIterableResponse([]) -export const mockedDisksResultItems: DiskAggregatedListData = { - data: { items: {} }, -} +export const mockedDisksResultItems = mockIterableResponse([]) -export const mockedDisksGetSSDDetails: DiskData = { - data: { +export const mockedDisksGetSSDDetails: [IDisk] = [ + { sizeGb: '20', type: 'https://www.googleapis.com/compute/v1/projects/techops-events/zones/us-central1-b/diskTypes/pd-standard-ssd', id: '12456789012', name: 'test-resource-name', }, -} +] -export const mockedDisksGetHDDDetails: DiskData = { - data: { +export const mockedDisksGetHDDDetails: [IDisk] = [ + { sizeGb: '20', type: 'https://www.googleapis.com/compute/v1/projects/techops-events/zones/us-central1-b/diskTypes/pd-standard', id: '12456789012', name: 'test-resource-name', }, -} +] -export const mockedInstanceGetItems: InstanceData = { - data: { +export const mockedInstanceGetItems: [IInstance] = [ + { machineType: 'https://www.googleapis.com/compute/v1/projects/test-project/zones/us-west1-b/machineTypes/n2-standard-32', disks: [], id: '12456789012', name: 'test-resource-name', }, -} +] -export const mockedInstanceGetItemsCurrent: InstanceData = { - data: { +export const mockedInstanceGetItemsCurrent: [IInstance] = [ + { machineType: 'https://www.googleapis.com/compute/v1/projects/test-project/zones/us-west1-b/machineTypes/e2-medium', disks: [], id: '12456789012', name: 'test-resource-name', }, -} +] -export const mockedInstanceGetItemsWithHDDDisks: InstanceData = { - data: { +export const mockedInstanceGetItemsWithHDDDisks: [IInstance] = [ + { machineType: 'https://www.googleapis.com/compute/v1/projects/test-project/zones/us-west1-b/machineTypes/n2-standard-32', disks: [ @@ -139,10 +117,10 @@ export const mockedInstanceGetItemsWithHDDDisks: InstanceData = { id: '12456789012', name: 'test-instance-name', }, -} +] -export const mockedInstanceGetItemsWithBothDisks: InstanceData = { - data: { +export const mockedInstanceGetItemsWithBothDisks: [IInstance] = [ + { machineType: 'https://www.googleapis.com/compute/v1/projects/test-project/zones/us-west1-b/machineTypes/n2-standard-32', disks: [ @@ -160,38 +138,38 @@ export const mockedInstanceGetItemsWithBothDisks: InstanceData = { id: '12456789012', name: 'test-instance-name', }, -} +] -export const mockedMachineTypesGetItems: MachineTypeData = { - data: { +export const mockedMachineTypesGetItems: [IMachineType] = [ + { guestCpus: 32, }, -} +] -export const mockedMachineTypesGetItemsCurrent: MachineTypeData = { - data: { +export const mockedMachineTypesGetItemsCurrent: [IMachineType] = [ + { guestCpus: 2, }, -} +] -export const mockedMachineTypesGetItemsNew: MachineTypeData = { - data: { +export const mockedMachineTypesGetItemsNew: [IMachineType] = [ + { guestCpus: 1, }, -} +] -export const mockedImageGetDetails: ImageDetails = { - data: { +export const mockedImageGetDetails: [IImage] = [ + { archiveSizeBytes: '580709696', id: '12456789012', name: 'test-resource-name', }, -} +] -export const mockedAddressGetDetails: AddressDetails = { - data: { +export const mockedAddressGetDetails: [IAddress] = [ + { id: '123456789012345', name: 'test-address', address: '38.141.210.105', }, -} +] diff --git a/packages/gcp/src/lib/BillingExportRow.ts b/packages/gcp/src/lib/BillingExportRow.ts index a2b2ffb16..4c00d4b6d 100644 --- a/packages/gcp/src/lib/BillingExportRow.ts +++ b/packages/gcp/src/lib/BillingExportRow.ts @@ -3,9 +3,9 @@ */ import { BillingDataRow } from '@cloud-carbon-footprint/core' -import { GCP_REGIONS } from './GCPRegions' import { BigQueryDate } from '@google-cloud/bigquery' import { configLoader, containsAny } from '@cloud-carbon-footprint/common' +import { GCP_REGIONS } from './GCPRegions' import { SERVICES_TO_OVERRIDE_USAGE_UNIT_AS_UNKNOWN } from './BillingExportTypes' import { SHARED_CORE_PROCESSORS } from './MachineTypes' diff --git a/packages/gcp/src/lib/Recommendations.ts b/packages/gcp/src/lib/Recommendations.ts index 548fdcd55..de41b8063 100644 --- a/packages/gcp/src/lib/Recommendations.ts +++ b/packages/gcp/src/lib/Recommendations.ts @@ -7,8 +7,8 @@ import { google as googleRecommender } from '@google-cloud/recommender/build/pro import { protos as googleCompute } from '@google-cloud/compute' import IRecommendation = googleRecommender.cloud.recommender.v1.IRecommendation import IImpact = googleRecommender.cloud.recommender.v1.IImpact -import Instance = googleCompute.google.cloud.compute.v1.Instance -import Disk = googleCompute.google.cloud.compute.v1.Disk +import Instance = googleCompute.google.cloud.compute.v1.IInstance +import Disk = googleCompute.google.cloud.compute.v1.IDisk import { COMPUTE_PROCESSOR_TYPES, ComputeEstimator, diff --git a/packages/gcp/src/lib/ServiceWrapper.ts b/packages/gcp/src/lib/ServiceWrapper.ts index e95bac47c..ca4f59f99 100644 --- a/packages/gcp/src/lib/ServiceWrapper.ts +++ b/packages/gcp/src/lib/ServiceWrapper.ts @@ -27,16 +27,10 @@ import MachineType = googleCompute.google.cloud.compute.v1.IMachineType import Disk = googleCompute.google.cloud.compute.v1.IDisk import Image = googleCompute.google.cloud.compute.v1.IImage import Address = googleCompute.google.cloud.compute.v1.IAddress -import InstancesScopedList = googleCompute.google.cloud.compute.v1.IInstancesScopedList -import DisksScopedList = googleCompute.google.cloud.compute.v1.IDisksScopedList -import AddressesScopedList = googleCompute.google.cloud.compute.v1.IAddressesScopedList +import { IterableScopedList } from './ServiceWrapperTypes' const RETRY_AFTER = 10 -type IterableScopedList = AsyncIterable< - [string, InstancesScopedList | DisksScopedList | AddressesScopedList] -> - export default class ServiceWrapper { private readonly serviceWrapperLogger: Logger private readonly noResultsOnPageMessage = 'NO_RESULTS_ON_PAGE' diff --git a/packages/gcp/src/lib/ServiceWrapperTypes.ts b/packages/gcp/src/lib/ServiceWrapperTypes.ts new file mode 100644 index 000000000..c976101ff --- /dev/null +++ b/packages/gcp/src/lib/ServiceWrapperTypes.ts @@ -0,0 +1,11 @@ +import { google } from '@google-cloud/compute/build/protos/protos' +import InstancesScopedList = google.cloud.compute.v1.IInstancesScopedList +import DisksScopedList = google.cloud.compute.v1.IDisksScopedList +import AddressesScopedList = google.cloud.compute.v1.AddressesScopedList + +export type IterableScopedList = AsyncIterable + +export type ScopedListResult = [ + string, + InstancesScopedList | DisksScopedList | AddressesScopedList, +] From 60ac4b5b3823bb67ffa4e2780c3539952070510b Mon Sep 17 00:00:00 2001 From: Ashutosh Krishna Date: Mon, 17 Apr 2023 13:18:29 +0530 Subject: [PATCH 039/251] [#705] Update documentation to indicate no IAM user role needed to run the app --- microsite/docs/ConnectingData/AWS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/microsite/docs/ConnectingData/AWS.md b/microsite/docs/ConnectingData/AWS.md index 10a4cfe5f..a544951d1 100644 --- a/microsite/docs/ConnectingData/AWS.md +++ b/microsite/docs/ConnectingData/AWS.md @@ -9,7 +9,7 @@ Your AWS account needs to be configured to generate Cost and Usage reports and s 1. Ensure your aws account has the correct permissions - - You will need an [IAM](https://aws.amazon.com/premiumsupport/knowledge-center/create-access-key/) user that can create access-keys and modify your billing settings. + - IAM permissions are not required to run the app. - You can use the CloudFormation template file [ccf-app.yaml](https://github.com/cloud-carbon-footprint/cloud-carbon-footprint/blob/trunk/cloudformation/ccf-app.yaml) to automate the creation of a role that allows the Cloud Carbon Footprint application to read Cost and Usage Reports via AWS Athena. Note: the section that asks you to specify the "AssumeRolePolicyDocument" is where you define the user or role that will have permissions to assume the "ccf-app" role. - This role name will be used for the value in the environment variable: `AWS_TARGET_ACCOUNT_ROLE_NAME` From 9e04408c48870aa9bb3810b9fedf4bca1fe7413e Mon Sep 17 00:00:00 2001 From: Ashutosh Krishna Date: Mon, 24 Apr 2023 15:25:13 +0530 Subject: [PATCH 040/251] [#705] Update documentation to indicate no IAM user role needed to run the app --- microsite/docs/ConnectingData/AWS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/microsite/docs/ConnectingData/AWS.md b/microsite/docs/ConnectingData/AWS.md index a544951d1..5a0c20b69 100644 --- a/microsite/docs/ConnectingData/AWS.md +++ b/microsite/docs/ConnectingData/AWS.md @@ -9,7 +9,7 @@ Your AWS account needs to be configured to generate Cost and Usage reports and s 1. Ensure your aws account has the correct permissions - - IAM permissions are not required to run the app. + - You will need an IAM role that can give you access to Billing data via the CUR and Athena. Note that it is not necessary to create a new IAM user or access key. - You can use the CloudFormation template file [ccf-app.yaml](https://github.com/cloud-carbon-footprint/cloud-carbon-footprint/blob/trunk/cloudformation/ccf-app.yaml) to automate the creation of a role that allows the Cloud Carbon Footprint application to read Cost and Usage Reports via AWS Athena. Note: the section that asks you to specify the "AssumeRolePolicyDocument" is where you define the user or role that will have permissions to assume the "ccf-app" role. - This role name will be used for the value in the environment variable: `AWS_TARGET_ACCOUNT_ROLE_NAME` From b2a130d696d83ec7769ecc1608531415fa1a096a Mon Sep 17 00:00:00 2001 From: Arik Smith Date: Tue, 25 Apr 2023 12:59:07 -0400 Subject: [PATCH 041/251] updates fixtures to be immutable and to be used in recs test --- .../gcp/src/__tests__/Recommendations.test.ts | 223 +++++++----------- .../gcp/src/__tests__/ServiceWrapper.test.ts | 29 +-- .../__tests__/fixtures/googleapis.fixtures.ts | 77 +++--- 3 files changed, 138 insertions(+), 191 deletions(-) diff --git a/packages/gcp/src/__tests__/Recommendations.test.ts b/packages/gcp/src/__tests__/Recommendations.test.ts index d28de4ef7..c65bc5a67 100644 --- a/packages/gcp/src/__tests__/Recommendations.test.ts +++ b/packages/gcp/src/__tests__/Recommendations.test.ts @@ -1,9 +1,14 @@ /* * © 2021 Thoughtworks, Inc. */ -import { compute as googleCompute } from 'googleapis/build/src/apis/compute' -import { auth as googleAuth } from 'googleapis/build/src/apis/iam' -import { APIEndpoint } from 'googleapis-common' +import { + AddressesClient, + DisksClient, + ImagesClient, + InstancesClient, + MachineTypesClient, +} from '@google-cloud/compute' +import { GoogleAuth } from 'google-auth-library' import { RecommenderClient } from '@google-cloud/recommender' import { ProjectsClient } from '@google-cloud/resource-manager' import { @@ -32,21 +37,21 @@ import { mockDeleteAddressRecommendationsEast, } from './fixtures/recommender.fixtures' import { - mockedAddressesResultItems, - mockedDisksResultItems, + mockAddressesResultItems, + mockDisksResultItems, mockedInstanceGetItems, mockedInstanceGetItemsCurrent, mockedInstanceGetItemsWithBothDisks, mockedInstanceGetItemsWithHDDDisks, - mockedInstanceResultItems, + mockInstanceResultItems, mockedMachineTypesGetItems, mockedMachineTypesGetItemsNew, mockedMachineTypesGetItemsCurrent, mockedDisksGetSSDDetails, mockedDisksGetHDDDetails, mockedImageGetDetails, - mockedInstanceGlobalResultItems, - mockedInstanceRegionsResultItems, + mockInstanceGlobalResultItems, + mockInstanceRegionsResultItems, mockedAddressGetDetails, } from './fixtures/googleapis.fixtures' @@ -70,10 +75,12 @@ jest.mock('@google-cloud/recommender', () => ({ })) describe('GCP Recommendations Service', () => { - let googleAuthClient: GoogleAuthClient - let googleComputeClient: APIEndpoint + let googleAuthClient: GoogleAuthClient, + serviceWrapper: ServiceWrapper, + mockGoogleCompute: any beforeEach(async () => { + const googleAuth = new GoogleAuth() const getClientSpy = jest.spyOn(googleAuth, 'getClient') ;(getClientSpy as jest.Mock).mockResolvedValue(jest.fn()) @@ -81,29 +88,47 @@ describe('GCP Recommendations Service', () => { googleAuthClient = await googleAuth.getClient({ scopes: ['https://www.googleapis.com/auth/cloud-platform'], }) - googleComputeClient = googleCompute('v1') + + mockGoogleCompute = { + instances: new InstancesClient(), + machineTypes: new MachineTypesClient(), + disks: new DisksClient(), + images: new ImagesClient(), + addresses: new AddressesClient(), + } setupSpy( - googleComputeClient.instances, - 'aggregatedList', - mockedInstanceResultItems, + mockGoogleCompute.instances, + 'aggregatedListAsync', + mockInstanceResultItems(), ) setupSpy( - googleComputeClient.disks, - 'aggregatedList', - mockedDisksResultItems, + mockGoogleCompute.disks, + 'aggregatedListAsync', + mockDisksResultItems(), ) setupSpy( - googleComputeClient.addresses, - 'aggregatedList', - mockedAddressesResultItems, + mockGoogleCompute.addresses, + 'aggregatedListAsync', + mockAddressesResultItems(), + ) + + serviceWrapper = new ServiceWrapper( + new ProjectsClient(), + googleAuthClient, + mockGoogleCompute.instances, + mockGoogleCompute.disks, + mockGoogleCompute.addresses, + mockGoogleCompute.images, + mockGoogleCompute.machineTypes, + new RecommenderClient(), ) }) describe('Stop VM Recommendations', () => { beforeEach(() => { setupSpy( - googleComputeClient.machineTypes, + mockGoogleCompute.machineTypes, 'get', mockedMachineTypesGetItems, ) @@ -113,18 +138,13 @@ describe('GCP Recommendations Service', () => { mockListRecommendations .mockResolvedValueOnce(mockStopVMRecommendationsResults) .mockResolvedValue([[]]) - setupSpy(googleComputeClient.instances, 'get', mockedInstanceGetItems) + setupSpy(mockGoogleCompute.instances, 'get', mockedInstanceGetItems) const recommendationsService = new Recommendations( new ComputeEstimator(), new StorageEstimator(GCP_CLOUD_CONSTANTS.HDDCOEFFICIENT), new StorageEstimator(GCP_CLOUD_CONSTANTS.SSDCOEFFICIENT), - new ServiceWrapper( - new ProjectsClient(), - googleAuthClient, - googleComputeClient, - new RecommenderClient(), - ), + serviceWrapper, ) const recommendations = await recommendationsService.getRecommendations() @@ -156,24 +176,19 @@ describe('GCP Recommendations Service', () => { ) .mockResolvedValue([[]]) - setupSpy(googleComputeClient.instances, 'get', mockedInstanceGetItems) + setupSpy(mockGoogleCompute.instances, 'get', mockedInstanceGetItems) setupSpy( - googleComputeClient.instances, - 'aggregatedList', - mockedInstanceGlobalResultItems, + mockGoogleCompute.instances, + 'aggregatedListAsync', + mockInstanceGlobalResultItems(), ) const recommendationsService = new Recommendations( new ComputeEstimator(), new StorageEstimator(GCP_CLOUD_CONSTANTS.HDDCOEFFICIENT), new StorageEstimator(GCP_CLOUD_CONSTANTS.SSDCOEFFICIENT), - new ServiceWrapper( - new ProjectsClient(), - googleAuthClient, - googleComputeClient, - new RecommenderClient(), - ), + serviceWrapper, ) const recommendations = await recommendationsService.getRecommendations() @@ -203,24 +218,19 @@ describe('GCP Recommendations Service', () => { .mockResolvedValueOnce(mockStopVMRecommendationsResults) .mockResolvedValue([[]]) - setupSpy(googleComputeClient.instances, 'get', mockedInstanceGetItems) + setupSpy(mockGoogleCompute.instances, 'get', mockedInstanceGetItems) setupSpy( - googleComputeClient.instances, - 'aggregatedList', - mockedInstanceRegionsResultItems, + mockGoogleCompute.instances, + 'aggregatedListAsync', + mockInstanceRegionsResultItems(), ) const recommendationsService = new Recommendations( new ComputeEstimator(), new StorageEstimator(GCP_CLOUD_CONSTANTS.HDDCOEFFICIENT), new StorageEstimator(GCP_CLOUD_CONSTANTS.SSDCOEFFICIENT), - new ServiceWrapper( - new ProjectsClient(), - googleAuthClient, - googleComputeClient, - new RecommenderClient(), - ), + serviceWrapper, ) const recommendations = await recommendationsService.getRecommendations() @@ -250,19 +260,14 @@ describe('GCP Recommendations Service', () => { .mockResolvedValueOnce(mockStopVMRecommendationsResults) .mockResolvedValue([[]]) - const targetFunctionSpy = jest.spyOn(googleComputeClient.instances, 'get') + const targetFunctionSpy = jest.spyOn(mockGoogleCompute.instances, 'get') ;(targetFunctionSpy as jest.Mock).mockRejectedValue('Error') const recommendationsService = new Recommendations( new ComputeEstimator(), new StorageEstimator(GCP_CLOUD_CONSTANTS.HDDCOEFFICIENT), new StorageEstimator(GCP_CLOUD_CONSTANTS.SSDCOEFFICIENT), - new ServiceWrapper( - new ProjectsClient(), - googleAuthClient, - googleComputeClient, - new RecommenderClient(), - ), + serviceWrapper, ) const recommendations = await recommendationsService.getRecommendations() @@ -292,22 +297,17 @@ describe('GCP Recommendations Service', () => { .mockResolvedValueOnce(mockStopVMRecommendationsResults) .mockResolvedValue([[]]) setupSpy( - googleComputeClient.instances, + mockGoogleCompute.instances, 'get', mockedInstanceGetItemsWithHDDDisks, ) - setupSpy(googleComputeClient.disks, 'get', mockedDisksGetHDDDetails) + setupSpy(mockGoogleCompute.disks, 'get', mockedDisksGetHDDDetails) const recommendationsService = new Recommendations( new ComputeEstimator(), new StorageEstimator(GCP_CLOUD_CONSTANTS.HDDCOEFFICIENT), new StorageEstimator(GCP_CLOUD_CONSTANTS.SSDCOEFFICIENT), - new ServiceWrapper( - new ProjectsClient(), - googleAuthClient, - googleComputeClient, - new RecommenderClient(), - ), + serviceWrapper, ) const recommendations = await recommendationsService.getRecommendations() @@ -337,13 +337,13 @@ describe('GCP Recommendations Service', () => { .mockResolvedValueOnce(mockStopVMRecommendationsResults) .mockResolvedValue([[]]) setupSpy( - googleComputeClient.instances, + mockGoogleCompute.instances, 'get', mockedInstanceGetItemsWithBothDisks, ) setupSpyWithMultipleValues( - googleComputeClient.disks, + mockGoogleCompute.disks, 'get', mockedDisksGetSSDDetails, mockedDisksGetHDDDetails, @@ -353,12 +353,7 @@ describe('GCP Recommendations Service', () => { new ComputeEstimator(), new StorageEstimator(GCP_CLOUD_CONSTANTS.HDDCOEFFICIENT), new StorageEstimator(GCP_CLOUD_CONSTANTS.SSDCOEFFICIENT), - new ServiceWrapper( - new ProjectsClient(), - googleAuthClient, - googleComputeClient, - new RecommenderClient(), - ), + serviceWrapper, ) const recommendations = await recommendationsService.getRecommendations() @@ -390,28 +385,19 @@ describe('GCP Recommendations Service', () => { .mockResolvedValue([[]]) setupSpyWithMultipleValues( - googleComputeClient.machineTypes, + mockGoogleCompute.machineTypes, 'get', mockedMachineTypesGetItemsCurrent, mockedMachineTypesGetItemsNew, ) - setupSpy( - googleComputeClient.instances, - 'get', - mockedInstanceGetItemsCurrent, - ) + setupSpy(mockGoogleCompute.instances, 'get', mockedInstanceGetItemsCurrent) const recommendationsService = new Recommendations( new ComputeEstimator(), new StorageEstimator(GCP_CLOUD_CONSTANTS.HDDCOEFFICIENT), new StorageEstimator(GCP_CLOUD_CONSTANTS.SSDCOEFFICIENT), - new ServiceWrapper( - new ProjectsClient(), - googleAuthClient, - googleComputeClient, - new RecommenderClient(), - ), + serviceWrapper, ) const recommendations = await recommendationsService.getRecommendations() @@ -442,25 +428,20 @@ describe('GCP Recommendations Service', () => { .mockResolvedValue([[]]) setupSpyWithMultipleValues( - googleComputeClient.machineTypes, + mockGoogleCompute.machineTypes, 'get', mockedMachineTypesGetItemsCurrent, mockedMachineTypesGetItemsNew, ) - const targetFunctionSpy = jest.spyOn(googleComputeClient.instances, 'get') + const targetFunctionSpy = jest.spyOn(mockGoogleCompute.instances, 'get') ;(targetFunctionSpy as jest.Mock).mockRejectedValue('Error') const recommendationsService = new Recommendations( new ComputeEstimator(), new StorageEstimator(GCP_CLOUD_CONSTANTS.HDDCOEFFICIENT), new StorageEstimator(GCP_CLOUD_CONSTANTS.SSDCOEFFICIENT), - new ServiceWrapper( - new ProjectsClient(), - googleAuthClient, - googleComputeClient, - new RecommenderClient(), - ), + serviceWrapper, ) const recommendations = await recommendationsService.getRecommendations() @@ -490,18 +471,13 @@ describe('GCP Recommendations Service', () => { .mockResolvedValueOnce(mockDeleteDiskRecommendationsResults) .mockResolvedValue([[]]) - setupSpy(googleComputeClient.disks, 'get', mockedDisksGetSSDDetails) + setupSpy(mockGoogleCompute.disks, 'get', mockedDisksGetSSDDetails) const recommendationsService = new Recommendations( new ComputeEstimator(), new StorageEstimator(GCP_CLOUD_CONSTANTS.HDDCOEFFICIENT), new StorageEstimator(GCP_CLOUD_CONSTANTS.SSDCOEFFICIENT), - new ServiceWrapper( - new ProjectsClient(), - googleAuthClient, - googleComputeClient, - new RecommenderClient(), - ), + serviceWrapper, ) const recommendations = await recommendationsService.getRecommendations() @@ -531,18 +507,13 @@ describe('GCP Recommendations Service', () => { .mockResolvedValueOnce(mockSnapshotAndDeleteDiskRecommendationsResults) .mockResolvedValue([[]]) - setupSpy(googleComputeClient.disks, 'get', mockedDisksGetHDDDetails) + setupSpy(mockGoogleCompute.disks, 'get', mockedDisksGetHDDDetails) const recommendationsService = new Recommendations( new ComputeEstimator(), new StorageEstimator(GCP_CLOUD_CONSTANTS.HDDCOEFFICIENT), new StorageEstimator(GCP_CLOUD_CONSTANTS.SSDCOEFFICIENT), - new ServiceWrapper( - new ProjectsClient(), - googleAuthClient, - googleComputeClient, - new RecommenderClient(), - ), + serviceWrapper, ) const recommendations = await recommendationsService.getRecommendations() @@ -572,18 +543,13 @@ describe('GCP Recommendations Service', () => { .mockResolvedValueOnce(mockDeleteImageRecommendationsResults) .mockResolvedValue([[]]) - setupSpy(googleComputeClient.images, 'get', mockedImageGetDetails) + setupSpy(mockGoogleCompute.images, 'get', mockedImageGetDetails) const recommendationsService = new Recommendations( new ComputeEstimator(), new StorageEstimator(GCP_CLOUD_CONSTANTS.HDDCOEFFICIENT), new StorageEstimator(GCP_CLOUD_CONSTANTS.SSDCOEFFICIENT), - new ServiceWrapper( - new ProjectsClient(), - googleAuthClient, - googleComputeClient, - new RecommenderClient(), - ), + serviceWrapper, ) const recommendations = await recommendationsService.getRecommendations() @@ -612,18 +578,13 @@ describe('GCP Recommendations Service', () => { .mockResolvedValueOnce(mockDeleteAddressRecommendationsResults) .mockResolvedValue([[]]) - setupSpy(googleComputeClient.addresses, 'get', mockedAddressGetDetails) + setupSpy(mockGoogleCompute.addresses, 'get', mockedAddressGetDetails) const recommendationsService = new Recommendations( new ComputeEstimator(), new StorageEstimator(GCP_CLOUD_CONSTANTS.HDDCOEFFICIENT), new StorageEstimator(GCP_CLOUD_CONSTANTS.SSDCOEFFICIENT), - new ServiceWrapper( - new ProjectsClient(), - googleAuthClient, - googleComputeClient, - new RecommenderClient(), - ), + serviceWrapper, ) const recommendations = await recommendationsService.getRecommendations() @@ -654,24 +615,15 @@ describe('GCP Recommendations Service', () => { .mockResolvedValueOnce(mockDeleteAddressRecommendationsEast) .mockResolvedValue([[]]) - setupSpy( - googleComputeClient.machineTypes, - 'get', - mockedMachineTypesGetItems, - ) - setupSpy(googleComputeClient.instances, 'get', mockedInstanceGetItems) - setupSpy(googleComputeClient.addresses, 'get', mockedAddressGetDetails) + setupSpy(mockGoogleCompute.machineTypes, 'get', mockedMachineTypesGetItems) + setupSpy(mockGoogleCompute.instances, 'get', mockedInstanceGetItems) + setupSpy(mockGoogleCompute.addresses, 'get', mockedAddressGetDetails) const recommendationsService = new Recommendations( new ComputeEstimator(), new StorageEstimator(GCP_CLOUD_CONSTANTS.HDDCOEFFICIENT), new StorageEstimator(GCP_CLOUD_CONSTANTS.SSDCOEFFICIENT), - new ServiceWrapper( - new ProjectsClient(), - googleAuthClient, - googleComputeClient, - new RecommenderClient(), - ), + serviceWrapper, ) const recommendations = await recommendationsService.getRecommendations() @@ -732,12 +684,7 @@ describe('GCP Recommendations Service', () => { new ComputeEstimator(), new StorageEstimator(GCP_CLOUD_CONSTANTS.HDDCOEFFICIENT), new StorageEstimator(GCP_CLOUD_CONSTANTS.SSDCOEFFICIENT), - new ServiceWrapper( - new ProjectsClient(), - googleAuthClient, - googleComputeClient, - new RecommenderClient(), - ), + serviceWrapper, ) const recommendations = await recommendationsService.getRecommendations() diff --git a/packages/gcp/src/__tests__/ServiceWrapper.test.ts b/packages/gcp/src/__tests__/ServiceWrapper.test.ts index 03408dd2c..4ae7899d0 100644 --- a/packages/gcp/src/__tests__/ServiceWrapper.test.ts +++ b/packages/gcp/src/__tests__/ServiceWrapper.test.ts @@ -15,14 +15,13 @@ import { GoogleAuth } from 'google-auth-library' import { RecommenderClient } from '@google-cloud/recommender' import { GoogleAuthClient, wait } from '@cloud-carbon-footprint/common' import { - InstanceData, - mockedAddressesResultItems, + mockAddressesResultItems, mockedAddressGetDetails, mockedDisksGetSSDDetails, - mockedDisksResultItems, + mockDisksResultItems, mockedImageGetDetails, mockedInstanceGetItems, - mockedInstanceResultItems, + mockInstanceResultItems, mockedMachineTypesGetItems, } from './fixtures/googleapis.fixtures' import { mockedProjects } from './fixtures/resourceManager.fixtures' @@ -86,18 +85,18 @@ describe('GCP Service Wrapper', () => { setupSpy( InstancesClient.prototype, 'aggregatedListAsync', - mockedInstanceResultItems, + mockInstanceResultItems(), ) setupSpy( DisksClient.prototype, 'aggregatedListAsync', - mockedDisksResultItems, + mockDisksResultItems(), ) setupSpy(DisksClient.prototype, 'get', mockedDisksGetSSDDetails) setupSpy( AddressesClient.prototype, 'aggregatedListAsync', - mockedAddressesResultItems, + mockAddressesResultItems(), ) setupSpy(MachineTypesClient.prototype, 'get', mockedMachineTypesGetItems) setupSpy(InstancesClient.prototype, 'get', mockedInstanceGetItems) @@ -157,17 +156,15 @@ describe('GCP Service Wrapper', () => { 'us-west1-b', ) - const expectedResult: InstanceData = { - data: { - machineType: - 'https://www.googleapis.com/compute/v1/projects/test-project/zones/us-west1-b/machineTypes/n2-standard-32', - disks: [], - id: '12456789012', - name: 'test-resource-name', - }, + const expectedResult: Instance = { + machineType: + 'https://www.googleapis.com/compute/v1/projects/test-project/zones/us-west1-b/machineTypes/n2-standard-32', + disks: [], + id: '12456789012', + name: 'test-resource-name', } - expect(instanceDetails).toEqual(expectedResult.data) + expect(instanceDetails).toEqual(expectedResult) }) it('gets machine type details', async () => { diff --git a/packages/gcp/src/__tests__/fixtures/googleapis.fixtures.ts b/packages/gcp/src/__tests__/fixtures/googleapis.fixtures.ts index 684df7b80..458c3d380 100644 --- a/packages/gcp/src/__tests__/fixtures/googleapis.fixtures.ts +++ b/packages/gcp/src/__tests__/fixtures/googleapis.fixtures.ts @@ -27,43 +27,46 @@ export const mockIterableResponse = ( return mockAsyncIterator[Symbol.asyncIterator](mockResponse) } -export const mockedInstanceResultItems = mockIterableResponse([ - [ - 'zones/us-west1-a', - { - instances: [{ id: 'test-instance' }], - }, - ], - [ - 'zones/us-east1-a', - { - instances: [{ id: 'test-instance-1' }], - }, - ], - ['zones/us-west1-b', { warning: { code: 'NO_RESULTS_ON_PAGE' } }], -]) - -export const mockedInstanceRegionsResultItems = mockIterableResponse([ - [ - 'regions/us-west1', - { - instances: [{ id: 'test-instance' }], - }, - ], -]) - -export const mockedInstanceGlobalResultItems = mockIterableResponse([ - [ - 'global', - { - instances: [{ id: 'test-instance-global' }], - }, - ], -]) - -export const mockedAddressesResultItems = mockIterableResponse([]) - -export const mockedDisksResultItems = mockIterableResponse([]) +export const mockInstanceResultItems = () => + mockIterableResponse([ + [ + 'zones/us-west1-a', + { + instances: [{ id: 'test-instance' }], + }, + ], + [ + 'zones/us-east1-a', + { + instances: [{ id: 'test-instance-1' }], + }, + ], + ['zones/us-west1-b', { warning: { code: 'NO_RESULTS_ON_PAGE' } }], + ]) + +export const mockInstanceRegionsResultItems = () => + mockIterableResponse([ + [ + 'regions/us-west1', + { + instances: [{ id: 'test-instance' }], + }, + ], + ]) + +export const mockInstanceGlobalResultItems = () => + mockIterableResponse([ + [ + 'global', + { + instances: [{ id: 'test-instance-global' }], + }, + ], + ]) + +export const mockAddressesResultItems = () => mockIterableResponse([]) + +export const mockDisksResultItems = () => mockIterableResponse([]) export const mockedDisksGetSSDDetails: [IDisk] = [ { From dbd1bebe836254ea8079e4c4bc7c4b2d1459380d Mon Sep 17 00:00:00 2001 From: Arik Smith Date: Tue, 25 Apr 2023 13:00:54 -0400 Subject: [PATCH 042/251] remove googleapis dependency --- packages/gcp/package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/gcp/package.json b/packages/gcp/package.json index 2a9f3aaa0..d8a4b5e14 100644 --- a/packages/gcp/package.json +++ b/packages/gcp/package.json @@ -49,7 +49,6 @@ "@google-cloud/recommender": "^4.2.5", "@google-cloud/resource-manager": "^3.0.0", "google-gax": "^2.29.0", - "googleapis": "^96.0.0", "moment": "^2.29.1" }, "devDependencies": { From e0299c2cae9340999d41f120d6b2600b1dc1a06e Mon Sep 17 00:00:00 2001 From: Arik Smith Date: Tue, 25 Apr 2023 14:28:29 -0400 Subject: [PATCH 043/251] changeset: Replaces the use of googleapis with separate package clients --- .changeset/brave-mugs-serve.md | 5 +++++ yarn.lock | 34 +--------------------------------- 2 files changed, 6 insertions(+), 33 deletions(-) create mode 100644 .changeset/brave-mugs-serve.md diff --git a/.changeset/brave-mugs-serve.md b/.changeset/brave-mugs-serve.md new file mode 100644 index 000000000..5ee531f09 --- /dev/null +++ b/.changeset/brave-mugs-serve.md @@ -0,0 +1,5 @@ +--- +'@cloud-carbon-footprint/gcp': minor +--- + +Replaces the use of googleapis with separate package clients diff --git a/yarn.lock b/yarn.lock index 431237869..511893b5e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2527,7 +2527,6 @@ __metadata: eslint-plugin-prettier: ^4.0.0 eslint-plugin-unused-imports: ^2.0.0 google-gax: ^2.29.0 - googleapis: ^96.0.0 jest: ^27.4.7 jest-when: ^3.5.0 lint-staged: ^12.1.7 @@ -12534,30 +12533,6 @@ __metadata: languageName: node linkType: hard -"googleapis-common@npm:^5.0.2": - version: 5.1.0 - resolution: "googleapis-common@npm:5.1.0" - dependencies: - extend: ^3.0.2 - gaxios: ^4.0.0 - google-auth-library: ^7.14.0 - qs: ^6.7.0 - url-template: ^2.0.8 - uuid: ^8.0.0 - checksum: 025daa078583f7bf6e92cf5d6e6ea804dce6ded84b1ead210803596b0b6d1fe9693069083302603173dc0c1f5e1125a501f2553952df60c001113a78ee055009 - languageName: node - linkType: hard - -"googleapis@npm:^96.0.0": - version: 96.0.0 - resolution: "googleapis@npm:96.0.0" - dependencies: - google-auth-library: ^7.0.2 - googleapis-common: ^5.0.2 - checksum: 77cfaf65ef29310db19a72351a97dccb7bcc3acd2c0f5f8c079f62c66b98295b620b8eed180a1c4da1f3ea74a1a8fbedbb039180adba36af6cadfeb9472f6bcb - languageName: node - linkType: hard - "got@npm:^9.6.0": version: 9.6.0 resolution: "got@npm:9.6.0" @@ -19137,7 +19112,7 @@ __metadata: languageName: node linkType: hard -"qs@npm:^6.10.3, qs@npm:^6.7.0, qs@npm:^6.9.4": +"qs@npm:^6.10.3, qs@npm:^6.9.4": version: 6.11.0 resolution: "qs@npm:6.11.0" dependencies: @@ -23207,13 +23182,6 @@ __metadata: languageName: node linkType: hard -"url-template@npm:^2.0.8": - version: 2.0.8 - resolution: "url-template@npm:2.0.8" - checksum: 4183fccd74e3591e4154134d4443dccecba9c455c15c7df774f1f1e3fa340fd9bffb903b5beec347196d15ce49c34edf6dec0634a95d170ad6e78c0467d6e13e - languageName: node - linkType: hard - "url@npm:0.10.3": version: 0.10.3 resolution: "url@npm:0.10.3" From f0ccbdb1b69bc3f4fd0ec0cdb308e3ab7e93358f Mon Sep 17 00:00:00 2001 From: Cam Casher Date: Wed, 26 Apr 2023 09:00:07 -0600 Subject: [PATCH 044/251] temp fix to update demo app data --- .github/workflows/demo-app.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/demo-app.yml b/.github/workflows/demo-app.yml index 28500e600..b51573e7f 100644 --- a/.github/workflows/demo-app.yml +++ b/.github/workflows/demo-app.yml @@ -2,6 +2,11 @@ name: Deploy Demo App on: workflow_dispatch: + push: + branches: [trunk] + paths-ignore: + - 'microsite/**' + - '.github/workflows/deploy-microsite.yml' pull_request: branches: [trunk] types: [closed] From ab29327f2ba9d4eccdb6314caa92716a74fe3955 Mon Sep 17 00:00:00 2001 From: Cam Casher Date: Wed, 26 Apr 2023 09:02:05 -0600 Subject: [PATCH 045/251] fix: remove cancel in progress for demo app --- .github/workflows/demo-app.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/demo-app.yml b/.github/workflows/demo-app.yml index b51573e7f..595f55864 100644 --- a/.github/workflows/demo-app.yml +++ b/.github/workflows/demo-app.yml @@ -15,7 +15,7 @@ on: - '.github/workflows/deploy-microsite.yml' concurrency: group: ${{ github.workflow }} - cancel-in-progress: true + # cancel-in-progress: true jobs: deploy-demo-app: if: github.event.pull_request.head.ref == 'changeset-release/trunk' From 20fb1a8135d7b7adc03c8625b2cd5ac5909d601e Mon Sep 17 00:00:00 2001 From: Cam Casher Date: Wed, 26 Apr 2023 09:21:56 -0600 Subject: [PATCH 046/251] remove all demo concurrency --- .github/workflows/demo-app.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/demo-app.yml b/.github/workflows/demo-app.yml index 595f55864..67acca6b2 100644 --- a/.github/workflows/demo-app.yml +++ b/.github/workflows/demo-app.yml @@ -13,8 +13,8 @@ on: paths-ignore: - 'microsite/**' - '.github/workflows/deploy-microsite.yml' -concurrency: - group: ${{ github.workflow }} +# concurrency: +# group: ${{ github.workflow }} # cancel-in-progress: true jobs: deploy-demo-app: From 6f35f5ab62daadbfe339a4bfd7303327d3b95814 Mon Sep 17 00:00:00 2001 From: Cam Casher Date: Wed, 26 Apr 2023 09:27:30 -0600 Subject: [PATCH 047/251] removes workflow condition to run on releases --- .github/workflows/demo-app.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/demo-app.yml b/.github/workflows/demo-app.yml index 67acca6b2..868ce99a0 100644 --- a/.github/workflows/demo-app.yml +++ b/.github/workflows/demo-app.yml @@ -18,7 +18,7 @@ on: # cancel-in-progress: true jobs: deploy-demo-app: - if: github.event.pull_request.head.ref == 'changeset-release/trunk' + # if: github.event.pull_request.head.ref == 'changeset-release/trunk' runs-on: ubuntu-latest container: image: node:16.19-alpine3.17 From b212f0ed5ba5230cdbf8d1e6d09b96b92a0bc195 Mon Sep 17 00:00:00 2001 From: Arik Smith Date: Mon, 1 May 2023 20:29:10 -0400 Subject: [PATCH 048/251] [#1127] remove version locks and update azure response structure to latest version --- package.json | 4 +- packages/azure/package.json | 10 +- .../azure/src/__tests__/AzureAccount.test.ts | 4 +- .../__tests__/ConsumptionDetailRow.test.ts | 6 +- .../__tests__/ConsumptionManagement.test.ts | 4 +- .../consumptionManagement.fixtures.ts | 1383 ++++++++--------- .../azure/src/lib/ConsumptionDetailRow.ts | 32 +- .../azure/src/lib/ConsumptionManagement.ts | 21 +- packages/azure/src/lib/ConsumptionTypes.ts | 7 +- packages/cli/package.json | 4 +- .../createLookupTable.test.ts.snap | 16 +- yarn.lock | 181 ++- 12 files changed, 804 insertions(+), 868 deletions(-) diff --git a/package.json b/package.json index 52507fe06..5132885ea 100644 --- a/package.json +++ b/package.json @@ -23,9 +23,7 @@ "@babel/core": "~7.16.7", "@types/eslint": "^8.2.1", "google-auth-library": "^7.11.0", - "typescript": "^4.6.2", - "@azure/arm-consumption": "9.1.0", - "@azure/core-client": "1.6.0" + "typescript": "^4.6.2" }, "workspaces": { "packages": [ diff --git a/packages/azure/package.json b/packages/azure/package.json index 5f8bd8e8f..294826bc5 100644 --- a/packages/azure/package.json +++ b/packages/azure/package.json @@ -41,11 +41,11 @@ "lint:fix": "eslint '*/**/*.ts' --quiet --fix" }, "dependencies": { - "@azure/arm-advisor": "^3.0.3", - "@azure/arm-consumption": "^9.1.0", - "@azure/arm-resources-subscriptions": "^2.0.1", - "@azure/identity": "^3.0.0", - "@azure/ms-rest-js": "^2.6.2", + "@azure/arm-advisor": "^3.1.0", + "@azure/arm-consumption": "^9.2.0", + "@azure/arm-resources-subscriptions": "^2.0.2", + "@azure/identity": "^3.1.4", + "@azure/ms-rest-js": "^2.6.6", "@azure/ms-rest-nodeauth": "^3.1.1", "@cloud-carbon-footprint/common": "^1.10.0", "@cloud-carbon-footprint/core": "^0.17.1", diff --git a/packages/azure/src/__tests__/AzureAccount.test.ts b/packages/azure/src/__tests__/AzureAccount.test.ts index b24a2364f..94bf4b08b 100644 --- a/packages/azure/src/__tests__/AzureAccount.test.ts +++ b/packages/azure/src/__tests__/AzureAccount.test.ts @@ -237,8 +237,8 @@ describe('Azure Account', () => { serviceName: 'Virtual Machines', region: 'uksouth', usageType: 'D2 v2/DS2 v2', - kilowattHours: 0.015380813559107052, - co2e: 0.0000034606830507990865, + kilowattHours: 0.011945378995278953, + co2e: 0.000004196172273564499, }, ] expect(result).toEqual(expectedResult) diff --git a/packages/azure/src/__tests__/ConsumptionDetailRow.test.ts b/packages/azure/src/__tests__/ConsumptionDetailRow.test.ts index 45cad1529..516154e6f 100644 --- a/packages/azure/src/__tests__/ConsumptionDetailRow.test.ts +++ b/packages/azure/src/__tests__/ConsumptionDetailRow.test.ts @@ -10,10 +10,8 @@ describe('ConsumptionDetailRow', () => { //given const computeConsumptionDetails: LegacyUsageDetail = { kind: null, - properties: { - meterDetails: { - meterName: 'test-usageType', - }, + meterDetails: { + meterName: 'test-usageType', }, } diff --git a/packages/azure/src/__tests__/ConsumptionManagement.test.ts b/packages/azure/src/__tests__/ConsumptionManagement.test.ts index 1d151960a..107c77baf 100644 --- a/packages/azure/src/__tests__/ConsumptionManagement.test.ts +++ b/packages/azure/src/__tests__/ConsumptionManagement.test.ts @@ -1225,8 +1225,8 @@ describe('Azure Consumption Management Service', () => { serviceName: 'Virtual Machines', region: 'uksouth', usageType: 'D2 v2/DS2 v2', - kilowattHours: 0.015380813559107052, - co2e: 0.0000034606830507990865, + kilowattHours: 0.011945378995278953, + co2e: 0.000004196172273564499, }, ] diff --git a/packages/azure/src/__tests__/fixtures/consumptionManagement.fixtures.ts b/packages/azure/src/__tests__/fixtures/consumptionManagement.fixtures.ts index 8bcc425a3..6c9f6fc82 100644 --- a/packages/azure/src/__tests__/fixtures/consumptionManagement.fixtures.ts +++ b/packages/azure/src/__tests__/fixtures/consumptionManagement.fixtures.ts @@ -30,20 +30,17 @@ export const mockConsumptionManagementResponseOne: UsageDetailResult[] = [ name: 'name', type: 'type', tags: {}, - properties: { - kind: 'legacy', - date: new Date('2020-11-02'), - quantity: 17, - cost: 5, - meterDetails: { - meterName: 'D2 v2/DS2 v2', - unitOfMeasure: '10 Hours', - meterCategory: 'Virtual Machines', - }, - subscriptionName: 'test-subscription', - resourceLocation: 'uksouth', - resourceGroup: 'test-resource-group', + date: new Date('2020-11-02'), + quantity: 17, + cost: 5, + meterDetails: { + meterName: 'D2 v2/DS2 v2', + unitOfMeasure: '10 Hours', + meterCategory: 'Virtual Machines', }, + subscriptionName: 'test-subscription', + resourceLocation: 'uksouth', + resourceGroup: 'test-resource-group', }, { id: 'test-subscription-id', @@ -51,20 +48,17 @@ export const mockConsumptionManagementResponseOne: UsageDetailResult[] = [ name: 'name', type: 'type', tags: {}, - properties: { - kind: 'legacy', - date: new Date('2020-11-02'), - quantity: 0.250004, - cost: 10, - meterDetails: { - meterName: 'D4as v4 Spot', - unitOfMeasure: '1 Hour', - meterCategory: 'Virtual Machines', - }, - subscriptionName: 'test-subscription', - resourceLocation: 'westeurope', - resourceGroup: 'test-resource-group', + date: new Date('2020-11-02'), + quantity: 0.250004, + cost: 10, + meterDetails: { + meterName: 'D4as v4 Spot', + unitOfMeasure: '1 Hour', + meterCategory: 'Virtual Machines', }, + subscriptionName: 'test-subscription', + resourceLocation: 'westeurope', + resourceGroup: 'test-resource-group', }, { id: 'test-subscription-id', @@ -72,20 +66,17 @@ export const mockConsumptionManagementResponseOne: UsageDetailResult[] = [ name: 'name', type: 'type', tags: {}, - properties: { - kind: 'legacy', - date: new Date('2020-11-02'), - quantity: 7, - cost: 10, - meterDetails: { - meterName: 'F1', - unitOfMeasure: '1 Hour', - meterCategory: 'Azure App Service', - }, - subscriptionName: 'test-subscription', - resourceLocation: 'CentralUS', - resourceGroup: 'test-resource-group', + date: new Date('2020-11-02'), + quantity: 7, + cost: 10, + meterDetails: { + meterName: 'F1', + unitOfMeasure: '1 Hour', + meterCategory: 'Azure App Service', }, + subscriptionName: 'test-subscription', + resourceLocation: 'CentralUS', + resourceGroup: 'test-resource-group', }, { id: 'test-subscription-id', @@ -93,20 +84,17 @@ export const mockConsumptionManagementResponseOne: UsageDetailResult[] = [ name: 'name', type: 'type', tags: {}, - properties: { - kind: 'legacy', - date: new Date('2020-11-03'), - quantity: 10, - cost: 12, - meterDetails: { - meterName: 'vCPU Duration', - unitOfMeasure: '1000 Hours', - meterCategory: 'Container Instances', - }, - subscriptionName: 'test-subscription', - resourceLocation: 'SouthCentralUS', - resourceGroup: 'test-resource-group', + date: new Date('2020-11-03'), + quantity: 10, + cost: 12, + meterDetails: { + meterName: 'vCPU Duration', + unitOfMeasure: '1000 Hours', + meterCategory: 'Container Instances', }, + subscriptionName: 'test-subscription', + resourceLocation: 'SouthCentralUS', + resourceGroup: 'test-resource-group', }, { id: 'test-subscription-id', @@ -114,20 +102,17 @@ export const mockConsumptionManagementResponseOne: UsageDetailResult[] = [ name: 'name', type: 'type', tags: {}, - properties: { - kind: 'legacy', - date: new Date('2020-11-03'), - quantity: 12, - cost: 12, - meterDetails: { - meterName: '2 vCore', - unitOfMeasure: '100 Hours', - meterCategory: 'Azure Database for MySQL', - }, - subscriptionName: 'test-subscription', - resourceLocation: 'Unknown', - resourceGroup: 'test-resource-group', + date: new Date('2020-11-03'), + quantity: 12, + cost: 12, + meterDetails: { + meterName: '2 vCore', + unitOfMeasure: '100 Hours', + meterCategory: 'Azure Database for MySQL', }, + subscriptionName: 'test-subscription', + resourceLocation: 'Unknown', + resourceGroup: 'test-resource-group', }, { id: 'test-subscription-id', @@ -135,20 +120,18 @@ export const mockConsumptionManagementResponseOne: UsageDetailResult[] = [ name: 'name', type: 'type', tags: {}, - properties: { - kind: 'legacy', - date: new Date('2020-11-03'), - quantity: 12, - cost: 12, - meterDetails: { - meterName: '1-2 vCPU VM Support', - unitOfMeasure: '100 Hours', - meterCategory: 'Virtual Machines Licenses', - }, - subscriptionName: 'test-subscription', - resourceLocation: 'ukwest', - resourceGroup: 'test-resource-group', + + date: new Date('2020-11-03'), + quantity: 12, + cost: 12, + meterDetails: { + meterName: '1-2 vCPU VM Support', + unitOfMeasure: '100 Hours', + meterCategory: 'Virtual Machines Licenses', }, + subscriptionName: 'test-subscription', + resourceLocation: 'ukwest', + resourceGroup: 'test-resource-group', }, { id: 'test-subscription-id', @@ -156,20 +139,18 @@ export const mockConsumptionManagementResponseOne: UsageDetailResult[] = [ name: 'name', type: 'type', tags: {}, - properties: { - kind: 'legacy', - date: new Date('2020-11-03'), - quantity: 12, - cost: 12, - meterDetails: { - meterName: 'VpnGw1', - unitOfMeasure: '100 Hours', - meterCategory: 'VPN Gateway', - }, - subscriptionName: 'test-subscription', - resourceLocation: 'Unknown', - resourceGroup: 'test-resource-group', + + date: new Date('2020-11-03'), + quantity: 12, + cost: 12, + meterDetails: { + meterName: 'VpnGw1', + unitOfMeasure: '100 Hours', + meterCategory: 'VPN Gateway', }, + subscriptionName: 'test-subscription', + resourceLocation: 'Unknown', + resourceGroup: 'test-resource-group', }, ] @@ -180,20 +161,18 @@ export const mockConsumptionManagementResponseTwo: UsageDetailResult[] = [ name: 'name', type: 'type', tags: {}, - properties: { - kind: 'legacy', - date: new Date('2020-11-02'), - quantity: 0.031248, - cost: 5, - meterDetails: { - meterName: 'S10 Disks', - unitOfMeasure: '1 /Month', - meterCategory: 'Storage', - }, - subscriptionName: 'test-subscription', - resourceLocation: 'uksouth', - resourceGroup: 'test-resource-group', + + date: new Date('2020-11-02'), + quantity: 0.031248, + cost: 5, + meterDetails: { + meterName: 'S10 Disks', + unitOfMeasure: '1 /Month', + meterCategory: 'Storage', }, + subscriptionName: 'test-subscription', + resourceLocation: 'uksouth', + resourceGroup: 'test-resource-group', }, { id: 'test-subscription-id', @@ -201,20 +180,18 @@ export const mockConsumptionManagementResponseTwo: UsageDetailResult[] = [ name: 'name', type: 'type', tags: {}, - properties: { - kind: 'legacy', - date: new Date('2020-11-02'), - quantity: 0.031248, - cost: 5, - meterDetails: { - meterName: 'P4 Disks', - unitOfMeasure: '1 /Month', - meterCategory: 'Storage', - }, - subscriptionName: 'test-subscription', - resourceLocation: 'uksouth', - resourceGroup: 'test-resource-group', + + date: new Date('2020-11-02'), + quantity: 0.031248, + cost: 5, + meterDetails: { + meterName: 'P4 Disks', + unitOfMeasure: '1 /Month', + meterCategory: 'Storage', }, + subscriptionName: 'test-subscription', + resourceLocation: 'uksouth', + resourceGroup: 'test-resource-group', }, { id: 'test-subscription-id', @@ -222,20 +199,18 @@ export const mockConsumptionManagementResponseTwo: UsageDetailResult[] = [ name: 'name', type: 'type', tags: {}, - properties: { - kind: 'legacy', - date: new Date('2020-11-02'), - quantity: 0.031248, - cost: 5, - meterDetails: { - meterName: 'E1 Disks', - unitOfMeasure: '100 /Month', - meterCategory: 'Storage', - }, - subscriptionName: 'test-subscription', - resourceLocation: 'westeurope', - resourceGroup: 'test-resource-group', + + date: new Date('2020-11-02'), + quantity: 0.031248, + cost: 5, + meterDetails: { + meterName: 'E1 Disks', + unitOfMeasure: '100 /Month', + meterCategory: 'Storage', }, + subscriptionName: 'test-subscription', + resourceLocation: 'westeurope', + resourceGroup: 'test-resource-group', }, { id: 'test-subscription-id', @@ -243,20 +218,18 @@ export const mockConsumptionManagementResponseTwo: UsageDetailResult[] = [ name: 'name', type: 'type', tags: {}, - properties: { - kind: 'legacy', - date: new Date('2020-11-02'), - quantity: 2, - cost: 5, - meterDetails: { - meterName: 'Data Stored', - unitOfMeasure: '10 GB/Month', - meterCategory: 'Azure Database for MySQL', - }, - subscriptionName: 'test-subscription', - resourceLocation: 'westeurope', - resourceGroup: 'test-resource-group', + + date: new Date('2020-11-02'), + quantity: 2, + cost: 5, + meterDetails: { + meterName: 'Data Stored', + unitOfMeasure: '10 GB/Month', + meterCategory: 'Azure Database for MySQL', }, + subscriptionName: 'test-subscription', + resourceLocation: 'westeurope', + resourceGroup: 'test-resource-group', }, { id: 'test-subscription-id', @@ -264,20 +237,18 @@ export const mockConsumptionManagementResponseTwo: UsageDetailResult[] = [ name: 'name', type: 'type', tags: {}, - properties: { - kind: 'legacy', - date: new Date('2020-11-02'), - quantity: 2, - cost: 5, - meterDetails: { - meterName: 'Basic Registry Unit', - unitOfMeasure: '30 /Day', - meterCategory: 'Container Registry', - }, - subscriptionName: 'test-subscription', - resourceLocation: 'westeurope', - resourceGroup: 'test-resource-group', + + date: new Date('2020-11-02'), + quantity: 2, + cost: 5, + meterDetails: { + meterName: 'Basic Registry Unit', + unitOfMeasure: '30 /Day', + meterCategory: 'Container Registry', }, + subscriptionName: 'test-subscription', + resourceLocation: 'westeurope', + resourceGroup: 'test-resource-group', }, { id: 'test-subscription-id', @@ -285,20 +256,18 @@ export const mockConsumptionManagementResponseTwo: UsageDetailResult[] = [ name: 'name', type: 'type', tags: {}, - properties: { - kind: 'legacy', - date: new Date('2020-11-02'), - quantity: 2, - cost: 5, - meterDetails: { - meterName: 'Server - Free', - unitOfMeasure: '1 /Month', - meterCategory: 'Storage', - }, - subscriptionName: 'test-subscription', - resourceLocation: 'westeurope', - resourceGroup: 'test-resource-group', + + date: new Date('2020-11-02'), + quantity: 2, + cost: 5, + meterDetails: { + meterName: 'Server - Free', + unitOfMeasure: '1 /Month', + meterCategory: 'Storage', }, + subscriptionName: 'test-subscription', + resourceLocation: 'westeurope', + resourceGroup: 'test-resource-group', }, { id: 'test-subscription-id', @@ -306,20 +275,18 @@ export const mockConsumptionManagementResponseTwo: UsageDetailResult[] = [ name: 'name', type: 'type', tags: {}, - properties: { - kind: 'legacy', - date: new Date('2020-11-02'), - quantity: 5, - cost: 5, - meterDetails: { - meterName: 'S30 Disk', - unitOfMeasure: '1 /Month', - meterCategory: 'HDInsight', - }, - subscriptionName: 'test-subscription', - resourceLocation: 'uksouth', - resourceGroup: 'test-resource-group', + + date: new Date('2020-11-02'), + quantity: 5, + cost: 5, + meterDetails: { + meterName: 'S30 Disk', + unitOfMeasure: '1 /Month', + meterCategory: 'HDInsight', }, + subscriptionName: 'test-subscription', + resourceLocation: 'uksouth', + resourceGroup: 'test-resource-group', }, { id: 'test-subscription-id', @@ -327,20 +294,18 @@ export const mockConsumptionManagementResponseTwo: UsageDetailResult[] = [ name: 'name', type: 'type', tags: {}, - properties: { - kind: 'legacy', - date: new Date('2020-11-02'), - quantity: 2, - cost: 5, - meterDetails: { - meterName: 'Data Stored', - unitOfMeasure: '1 TB/Month', - meterCategory: 'Azure Synapse Analytics', - }, - subscriptionName: 'test-subscription', - resourceLocation: 'uksouth', - resourceGroup: 'test-resource-group', + + date: new Date('2020-11-02'), + quantity: 2, + cost: 5, + meterDetails: { + meterName: 'Data Stored', + unitOfMeasure: '1 TB/Month', + meterCategory: 'Azure Synapse Analytics', }, + subscriptionName: 'test-subscription', + resourceLocation: 'uksouth', + resourceGroup: 'test-resource-group', }, { id: 'test-subscription-id', @@ -348,20 +313,18 @@ export const mockConsumptionManagementResponseTwo: UsageDetailResult[] = [ name: 'name', type: 'type', tags: {}, - properties: { - kind: 'legacy', - date: new Date('2020-11-02'), - quantity: 10, - cost: 5, - meterDetails: { - meterName: 'ZRS Snapshots', - unitOfMeasure: '100 GB/Month', - meterCategory: 'Storage', - }, - subscriptionName: 'test-subscription', - resourceLocation: 'centralindia', - resourceGroup: 'test-resource-group', + + date: new Date('2020-11-02'), + quantity: 10, + cost: 5, + meterDetails: { + meterName: 'ZRS Snapshots', + unitOfMeasure: '100 GB/Month', + meterCategory: 'Storage', }, + subscriptionName: 'test-subscription', + resourceLocation: 'centralindia', + resourceGroup: 'test-resource-group', }, { id: 'test-subscription-id', @@ -369,20 +332,18 @@ export const mockConsumptionManagementResponseTwo: UsageDetailResult[] = [ name: 'name', type: 'type', tags: {}, - properties: { - kind: 'legacy', - date: new Date('2020-11-02'), - quantity: 1, - cost: 1, - meterDetails: { - meterName: 'P4 LRS Disk', - unitOfMeasure: '1 /Month', - meterCategory: 'Storage', - }, - subscriptionName: 'test-subscription', - resourceLocation: 'Unknown', - resourceGroup: 'test-resource-group', + + date: new Date('2020-11-02'), + quantity: 1, + cost: 1, + meterDetails: { + meterName: 'P4 LRS Disk', + unitOfMeasure: '1 /Month', + meterCategory: 'Storage', }, + subscriptionName: 'test-subscription', + resourceLocation: 'Unknown', + resourceGroup: 'test-resource-group', }, ] @@ -393,20 +354,18 @@ export const mockConsumptionManagementResponseThree: UsageDetailResult[] = [ name: 'name', type: 'type', tags: {}, - properties: { - kind: 'legacy', - date: new Date('2020-11-02'), - quantity: 10, - cost: 5, - meterDetails: { - meterName: 'Geo-Replication Data transfer', - unitOfMeasure: '1 GB', - meterCategory: 'Storage', - }, - subscriptionName: 'test-subscription', - resourceLocation: 'uksouth', - resourceGroup: 'test-resource-group', + + date: new Date('2020-11-02'), + quantity: 10, + cost: 5, + meterDetails: { + meterName: 'Geo-Replication Data transfer', + unitOfMeasure: '1 GB', + meterCategory: 'Storage', }, + subscriptionName: 'test-subscription', + resourceLocation: 'uksouth', + resourceGroup: 'test-resource-group', }, { id: 'test-subscription-id', @@ -414,20 +373,18 @@ export const mockConsumptionManagementResponseThree: UsageDetailResult[] = [ name: 'name', type: 'type', tags: {}, - properties: { - kind: 'legacy', - date: new Date('2020-11-02'), - quantity: 10, - cost: 5, - meterDetails: { - meterName: 'Geo-Replication Data transfer', - unitOfMeasure: '1 TB', - meterCategory: 'Bandwidth', - }, - subscriptionName: 'test-subscription', - resourceLocation: 'uksouth', - resourceGroup: 'test-resource-group', + + date: new Date('2020-11-02'), + quantity: 10, + cost: 5, + meterDetails: { + meterName: 'Geo-Replication Data transfer', + unitOfMeasure: '1 TB', + meterCategory: 'Bandwidth', }, + subscriptionName: 'test-subscription', + resourceLocation: 'uksouth', + resourceGroup: 'test-resource-group', }, { id: 'test-subscription-id', @@ -435,20 +392,18 @@ export const mockConsumptionManagementResponseThree: UsageDetailResult[] = [ name: 'name', type: 'type', tags: {}, - properties: { - kind: 'legacy', - date: new Date('2020-11-02'), - quantity: 5, - cost: 5, - meterDetails: { - meterName: 'Data Transfer Out - ASIA To Any', - unitOfMeasure: '1 TB', - meterCategory: 'Bandwidth', - }, - subscriptionName: 'test-subscription', - resourceLocation: 'uksouth', - resourceGroup: 'test-resource-group', + + date: new Date('2020-11-02'), + quantity: 5, + cost: 5, + meterDetails: { + meterName: 'Data Transfer Out - ASIA To Any', + unitOfMeasure: '1 TB', + meterCategory: 'Bandwidth', }, + subscriptionName: 'test-subscription', + resourceLocation: 'uksouth', + resourceGroup: 'test-resource-group', }, ] @@ -459,20 +414,18 @@ export const mockConsumptionManagementResponseFour: UsageDetailResult[] = [ name: 'name', type: 'type', tags: {}, - properties: { - kind: 'legacy', - date: new Date('2020-11-03'), - quantity: 12, - cost: 12, - meterDetails: { - meterName: 'Standard All-purpose Compute DBU', - unitOfMeasure: '10 Hours', - meterCategory: 'Azure Databricks', - }, - subscriptionName: 'test-subscription', - resourceLocation: 'Unassigned', - resourceGroup: 'test-resource-group', + + date: new Date('2020-11-03'), + quantity: 12, + cost: 12, + meterDetails: { + meterName: 'Standard All-purpose Compute DBU', + unitOfMeasure: '10 Hours', + meterCategory: 'Azure Databricks', }, + subscriptionName: 'test-subscription', + resourceLocation: 'Unassigned', + resourceGroup: 'test-resource-group', }, { id: 'test-subscription-id', @@ -480,20 +433,18 @@ export const mockConsumptionManagementResponseFour: UsageDetailResult[] = [ name: 'name', type: 'type', tags: {}, - properties: { - kind: 'legacy', - date: new Date('2020-11-03'), - quantity: 12, - cost: 12, - meterDetails: { - meterName: 'Developer Units', - unitOfMeasure: '100 Hours', - meterCategory: 'API Management', - }, - subscriptionName: 'test-subscription', - resourceLocation: 'uksouth', - resourceGroup: 'test-resource-group', + + date: new Date('2020-11-03'), + quantity: 12, + cost: 12, + meterDetails: { + meterName: 'Developer Units', + unitOfMeasure: '100 Hours', + meterCategory: 'API Management', }, + subscriptionName: 'test-subscription', + resourceLocation: 'uksouth', + resourceGroup: 'test-resource-group', }, { id: 'test-subscription-id', @@ -501,20 +452,18 @@ export const mockConsumptionManagementResponseFour: UsageDetailResult[] = [ name: 'name', type: 'type', tags: {}, - properties: { - kind: 'legacy', - date: new Date('2020-11-03'), - quantity: 12, - cost: 12, - meterDetails: { - meterName: '6 vCPU VM License', - unitOfMeasure: '100 Hours', - meterCategory: 'Virtual Machines Licenses', - }, - subscriptionName: 'test-subscription', - resourceLocation: 'uksouth', - resourceGroup: 'test-resource-group', + + date: new Date('2020-11-03'), + quantity: 12, + cost: 12, + meterDetails: { + meterName: '6 vCPU VM License', + unitOfMeasure: '100 Hours', + meterCategory: 'Virtual Machines Licenses', }, + subscriptionName: 'test-subscription', + resourceLocation: 'uksouth', + resourceGroup: 'test-resource-group', }, { id: 'test-subscription-id', @@ -522,20 +471,18 @@ export const mockConsumptionManagementResponseFour: UsageDetailResult[] = [ name: 'name', type: 'type', tags: {}, - properties: { - kind: 'legacy', - date: new Date('2020-11-03'), - quantity: 12, - cost: 12, - meterDetails: { - meterName: 'Standard Node', - unitOfMeasure: '1 /Month', - meterCategory: 'Advanced Data Security', - }, - subscriptionName: 'test-subscription', - resourceLocation: 'uksouth', - resourceGroup: 'test-resource-group', + + date: new Date('2020-11-03'), + quantity: 12, + cost: 12, + meterDetails: { + meterName: 'Standard Node', + unitOfMeasure: '1 /Month', + meterCategory: 'Advanced Data Security', }, + subscriptionName: 'test-subscription', + resourceLocation: 'uksouth', + resourceGroup: 'test-resource-group', }, ] export const mockConsumptionManagementResponseFive: UsageDetailResult[] = [ @@ -545,20 +492,18 @@ export const mockConsumptionManagementResponseFive: UsageDetailResult[] = [ name: 'name', type: 'type', tags: {}, - properties: { - kind: 'legacy', - date: new Date('2020-11-03'), - quantity: 5, - cost: 20, - meterDetails: { - meterName: 'D8 v3 Spot', - unitOfMeasure: '1 Hour', - meterCategory: 'Virtual Machines', - }, - subscriptionName: 'test-subscription', - resourceLocation: 'northeurope', - resourceGroup: 'test-resource-group', + + date: new Date('2020-11-03'), + quantity: 5, + cost: 20, + meterDetails: { + meterName: 'D8 v3 Spot', + unitOfMeasure: '1 Hour', + meterCategory: 'Virtual Machines', }, + subscriptionName: 'test-subscription', + resourceLocation: 'northeurope', + resourceGroup: 'test-resource-group', }, { id: 'test-subscription-id', @@ -566,20 +511,18 @@ export const mockConsumptionManagementResponseFive: UsageDetailResult[] = [ name: 'name', type: 'type', tags: {}, - properties: { - kind: 'legacy', - date: new Date('2020-11-03'), - quantity: 48, - cost: 5, - meterDetails: { - meterName: 'C1 Cache Instance', - unitOfMeasure: '100 Hours', - meterCategory: 'Redis Cache', - }, - subscriptionName: 'test-subscription', - resourceLocation: 'apsoutheast', - resourceGroup: 'test-resource-group', + + date: new Date('2020-11-03'), + quantity: 48, + cost: 5, + meterDetails: { + meterName: 'C1 Cache Instance', + unitOfMeasure: '100 Hours', + meterCategory: 'Redis Cache', }, + subscriptionName: 'test-subscription', + resourceLocation: 'apsoutheast', + resourceGroup: 'test-resource-group', }, { id: 'test-subscription-id', @@ -587,20 +530,18 @@ export const mockConsumptionManagementResponseFive: UsageDetailResult[] = [ name: 'name', type: 'type', tags: {}, - properties: { - kind: 'legacy', - date: new Date('2020-11-02'), - quantity: 0.111375, - cost: 10, - meterDetails: { - meterName: 'Execution Time', - unitOfMeasure: '50000 GB Seconds', - meterCategory: 'Functions', - }, - subscriptionName: 'test-subscription', - resourceLocation: 'EastUS2', - resourceGroup: 'test-resource-group', + + date: new Date('2020-11-02'), + quantity: 0.111375, + cost: 10, + meterDetails: { + meterName: 'Execution Time', + unitOfMeasure: '50000 GB Seconds', + meterCategory: 'Functions', }, + subscriptionName: 'test-subscription', + resourceLocation: 'EastUS2', + resourceGroup: 'test-resource-group', }, { id: 'test-subscription-id', @@ -608,20 +549,18 @@ export const mockConsumptionManagementResponseFive: UsageDetailResult[] = [ name: 'name', type: 'type', tags: {}, - properties: { - kind: 'legacy', - date: new Date('2020-11-02'), - quantity: 14.44976379, - cost: 7, - meterDetails: { - meterName: 'Memory Duration', - unitOfMeasure: '1000 GB Hours', - meterCategory: 'Container Instances', - }, - subscriptionName: 'test-subscription', - resourceLocation: 'ukwest', - resourceGroup: 'test-resource-group', + + date: new Date('2020-11-02'), + quantity: 14.44976379, + cost: 7, + meterDetails: { + meterName: 'Memory Duration', + unitOfMeasure: '1000 GB Hours', + meterCategory: 'Container Instances', }, + subscriptionName: 'test-subscription', + resourceLocation: 'ukwest', + resourceGroup: 'test-resource-group', }, ] @@ -632,20 +571,18 @@ export const mockConsumptionManagementResponseSix: UsageDetailResult[] = [ name: 'name', type: 'type', tags: {}, - properties: { - kind: 'legacy', - date: new Date('2020-11-03'), - quantity: 0.002654, - cost: 20, - meterDetails: { - meterName: 'Hot LRS Data Stored', - unitOfMeasure: '100 GB/Month', - meterCategory: 'Storage', - }, - subscriptionName: 'test-subscription', - resourceLocation: 'WestUS', - resourceGroup: 'test-resource-group', + + date: new Date('2020-11-03'), + quantity: 0.002654, + cost: 20, + meterDetails: { + meterName: 'Hot LRS Data Stored', + unitOfMeasure: '100 GB/Month', + meterCategory: 'Storage', }, + subscriptionName: 'test-subscription', + resourceLocation: 'WestUS', + resourceGroup: 'test-resource-group', }, { id: 'test-subscription-id', @@ -653,20 +590,18 @@ export const mockConsumptionManagementResponseSix: UsageDetailResult[] = [ name: 'name', type: 'type', tags: {}, - properties: { - kind: 'legacy', - date: new Date('2020-11-03'), - quantity: 0.012219, - cost: 15, - meterDetails: { - meterName: 'Cool ZRS Data Stored', - unitOfMeasure: '1 GB/Month', - meterCategory: 'Storage', - }, - subscriptionName: 'test-subscription', - resourceLocation: 'SouthCentralUS', - resourceGroup: 'test-resource-group', + + date: new Date('2020-11-03'), + quantity: 0.012219, + cost: 15, + meterDetails: { + meterName: 'Cool ZRS Data Stored', + unitOfMeasure: '1 GB/Month', + meterCategory: 'Storage', }, + subscriptionName: 'test-subscription', + resourceLocation: 'SouthCentralUS', + resourceGroup: 'test-resource-group', }, { id: 'test-subscription-id', @@ -674,20 +609,18 @@ export const mockConsumptionManagementResponseSix: UsageDetailResult[] = [ name: 'name', type: 'type', tags: {}, - properties: { - kind: 'legacy', - date: new Date('2020-11-03'), - quantity: 0.000015, - cost: 10, - meterDetails: { - meterName: 'RA-GRS Data Stored', - unitOfMeasure: '100 GB/Month', - meterCategory: 'Storage', - }, - subscriptionName: 'test-subscription', - resourceLocation: 'uksouth', - resourceGroup: 'test-resource-group', + + date: new Date('2020-11-03'), + quantity: 0.000015, + cost: 10, + meterDetails: { + meterName: 'RA-GRS Data Stored', + unitOfMeasure: '100 GB/Month', + meterCategory: 'Storage', }, + subscriptionName: 'test-subscription', + resourceLocation: 'uksouth', + resourceGroup: 'test-resource-group', }, { id: 'test-subscription-id', @@ -695,20 +628,18 @@ export const mockConsumptionManagementResponseSix: UsageDetailResult[] = [ name: 'name', type: 'type', tags: {}, - properties: { - kind: 'legacy', - date: new Date('2020-11-03'), - quantity: 0.027492, - cost: 5, - meterDetails: { - meterName: 'Cool RA-GZRS Data Stored', - unitOfMeasure: '1 GB/Month', - meterCategory: 'Storage', - }, - subscriptionName: 'test-subscription', - resourceLocation: 'uswest2', - resourceGroup: 'test-resource-group', + + date: new Date('2020-11-03'), + quantity: 0.027492, + cost: 5, + meterDetails: { + meterName: 'Cool RA-GZRS Data Stored', + unitOfMeasure: '1 GB/Month', + meterCategory: 'Storage', }, + subscriptionName: 'test-subscription', + resourceLocation: 'uswest2', + resourceGroup: 'test-resource-group', }, { id: 'test-subscription-id', @@ -716,20 +647,18 @@ export const mockConsumptionManagementResponseSix: UsageDetailResult[] = [ name: 'name', type: 'type', tags: {}, - properties: { - kind: 'legacy', - date: new Date('2020-11-04'), - quantity: 0.2973480663, - cost: 2, - meterDetails: { - meterName: 'P10 Disks', - unitOfMeasure: '1 /Month', - meterCategory: 'Storage', - }, - subscriptionName: 'test-subscription', - resourceLocation: 'uksouth', - resourceGroup: 'test-resource-group', + + date: new Date('2020-11-04'), + quantity: 0.2973480663, + cost: 2, + meterDetails: { + meterName: 'P10 Disks', + unitOfMeasure: '1 /Month', + meterCategory: 'Storage', }, + subscriptionName: 'test-subscription', + resourceLocation: 'uksouth', + resourceGroup: 'test-resource-group', }, { id: 'test-subscription-id', @@ -737,20 +666,18 @@ export const mockConsumptionManagementResponseSix: UsageDetailResult[] = [ name: 'name', type: 'type', tags: {}, - properties: { - kind: 'legacy', - date: new Date('2020-11-04'), - quantity: 123456789, - cost: 2, - meterDetails: { - meterName: 'S4 Disks', - unitOfMeasure: '1 /Month', - meterCategory: 'Storage', - }, - subscriptionName: 'test-subscription', - resourceLocation: 'westindia', - resourceGroup: 'test-resource-group', + + date: new Date('2020-11-04'), + quantity: 123456789, + cost: 2, + meterDetails: { + meterName: 'S4 Disks', + unitOfMeasure: '1 /Month', + meterCategory: 'Storage', }, + subscriptionName: 'test-subscription', + resourceLocation: 'westindia', + resourceGroup: 'test-resource-group', }, ] @@ -761,20 +688,18 @@ export const mockConsumptionManagementResponseSeven: UsageDetailResult[] = [ name: 'name', type: 'type', tags: {}, - properties: { - kind: 'legacy', - date: new Date('2020-11-03'), - quantity: 0.01344086022, - cost: 20, - meterDetails: { - meterName: 'Data Stored', - unitOfMeasure: '10 GB/Month', - meterCategory: 'Azure Database for MySQL', - }, - subscriptionName: 'test-subscription', - resourceLocation: 'uksouth', - resourceGroup: 'test-resource-group', + + date: new Date('2020-11-03'), + quantity: 0.01344086022, + cost: 20, + meterDetails: { + meterName: 'Data Stored', + unitOfMeasure: '10 GB/Month', + meterCategory: 'Azure Database for MySQL', }, + subscriptionName: 'test-subscription', + resourceLocation: 'uksouth', + resourceGroup: 'test-resource-group', }, { id: 'test-subscription-id', @@ -782,20 +707,18 @@ export const mockConsumptionManagementResponseSeven: UsageDetailResult[] = [ name: 'name', type: 'type', tags: {}, - properties: { - kind: 'legacy', - date: new Date('2020-11-04'), - quantity: 12, - cost: 30, - meterDetails: { - meterName: '2 vCore', - unitOfMeasure: '100 Hours', - meterCategory: 'Azure Database for MySQL', - }, - subscriptionName: 'test-subscription', - resourceLocation: 'uksouth', - resourceGroup: 'test-resource-group', + + date: new Date('2020-11-04'), + quantity: 12, + cost: 30, + meterDetails: { + meterName: '2 vCore', + unitOfMeasure: '100 Hours', + meterCategory: 'Azure Database for MySQL', }, + subscriptionName: 'test-subscription', + resourceLocation: 'uksouth', + resourceGroup: 'test-resource-group', }, { id: 'test-subscription-id', @@ -803,20 +726,18 @@ export const mockConsumptionManagementResponseSeven: UsageDetailResult[] = [ name: 'name', type: 'type', tags: {}, - properties: { - kind: 'legacy', - date: new Date('2020-11-04'), - quantity: 0.02150770413, - cost: 35, - meterDetails: { - meterName: 'Data Stored', - unitOfMeasure: '10 GB/Month', - meterCategory: 'Azure Cosmos DB', - }, - subscriptionName: 'test-subscription', - resourceLocation: 'CentralUS', - resourceGroup: 'test-resource-group', + + date: new Date('2020-11-04'), + quantity: 0.02150770413, + cost: 35, + meterDetails: { + meterName: 'Data Stored', + unitOfMeasure: '10 GB/Month', + meterCategory: 'Azure Cosmos DB', }, + subscriptionName: 'test-subscription', + resourceLocation: 'CentralUS', + resourceGroup: 'test-resource-group', }, { id: 'test-subscription-id', @@ -824,20 +745,18 @@ export const mockConsumptionManagementResponseSeven: UsageDetailResult[] = [ name: 'name', type: 'type', tags: {}, - properties: { - kind: 'legacy', - date: new Date('2020-11-04'), - quantity: 0.006989247310827, - cost: 40, - meterDetails: { - meterName: 'Data Stored', - unitOfMeasure: '10 GB/Month', - meterCategory: 'SQL Database', - }, - subscriptionName: 'test-subscription', - resourceLocation: 'uksouth', - resourceGroup: 'test-resource-group', + + date: new Date('2020-11-04'), + quantity: 0.006989247310827, + cost: 40, + meterDetails: { + meterName: 'Data Stored', + unitOfMeasure: '10 GB/Month', + meterCategory: 'SQL Database', }, + subscriptionName: 'test-subscription', + resourceLocation: 'uksouth', + resourceGroup: 'test-resource-group', }, { id: 'test-subscription-id', @@ -845,20 +764,18 @@ export const mockConsumptionManagementResponseSeven: UsageDetailResult[] = [ name: 'name', type: 'type', tags: {}, - properties: { - kind: 'legacy', - date: new Date('2020-11-03'), - quantity: 2, - cost: 45, - meterDetails: { - meterName: 'vCore', - unitOfMeasure: '10 Hours', - meterCategory: 'SQL Database', - }, - subscriptionName: 'test-subscription', - resourceLocation: 'uksouth', - resourceGroup: 'test-resource-group', + + date: new Date('2020-11-03'), + quantity: 2, + cost: 45, + meterDetails: { + meterName: 'vCore', + unitOfMeasure: '10 Hours', + meterCategory: 'SQL Database', }, + subscriptionName: 'test-subscription', + resourceLocation: 'uksouth', + resourceGroup: 'test-resource-group', }, ] @@ -869,20 +786,18 @@ export const mockConsumptionManagementResponseEight: UsageDetailResult[] = [ name: 'name', type: 'type', tags: {}, - properties: { - kind: 'legacy', - date: new Date('2020-11-02'), - quantity: 5, - cost: 5, - meterDetails: { - meterName: 'D8 v3', - unitOfMeasure: '1 Hour', - meterCategory: 'Virtual Machines', - }, - subscriptionName: 'test-subscription', - resourceLocation: 'northeurope', - resourceGroup: 'test-resource-group', + + date: new Date('2020-11-02'), + quantity: 5, + cost: 5, + meterDetails: { + meterName: 'D8 v3', + unitOfMeasure: '1 Hour', + meterCategory: 'Virtual Machines', }, + subscriptionName: 'test-subscription', + resourceLocation: 'northeurope', + resourceGroup: 'test-resource-group', }, { id: 'test-subscription-id', @@ -890,20 +805,18 @@ export const mockConsumptionManagementResponseEight: UsageDetailResult[] = [ name: 'name', type: 'type', tags: {}, - properties: { - kind: 'legacy', - date: new Date('2020-11-02'), - quantity: 5, - cost: 1.579140496, - meterDetails: { - meterName: 'Developer Units', - unitOfMeasure: '100 Hours', - meterCategory: 'API Management', - }, - subscriptionName: 'test-subscription', - resourceLocation: 'northeurope', - resourceGroup: 'test-resource-group', + + date: new Date('2020-11-02'), + quantity: 5, + cost: 1.579140496, + meterDetails: { + meterName: 'Developer Units', + unitOfMeasure: '100 Hours', + meterCategory: 'API Management', }, + subscriptionName: 'test-subscription', + resourceLocation: 'northeurope', + resourceGroup: 'test-resource-group', }, { id: 'test-subscription-id', @@ -911,20 +824,18 @@ export const mockConsumptionManagementResponseEight: UsageDetailResult[] = [ name: 'name', type: 'type', tags: {}, - properties: { - kind: 'legacy', - date: new Date('2020-11-03'), - quantity: 0.02150770413, - cost: 35, - meterDetails: { - meterName: 'Data Stored', - unitOfMeasure: '10 GB/Month', - meterCategory: 'Azure Cosmos DB', - }, - subscriptionName: 'test-subscription', - resourceLocation: 'northeurope', - resourceGroup: 'test-resource-group', + + date: new Date('2020-11-03'), + quantity: 0.02150770413, + cost: 35, + meterDetails: { + meterName: 'Data Stored', + unitOfMeasure: '10 GB/Month', + meterCategory: 'Azure Cosmos DB', }, + subscriptionName: 'test-subscription', + resourceLocation: 'northeurope', + resourceGroup: 'test-resource-group', }, { id: 'test-subscription-id', @@ -932,20 +843,18 @@ export const mockConsumptionManagementResponseEight: UsageDetailResult[] = [ name: 'name', type: 'type', tags: {}, - properties: { - kind: 'legacy', - date: new Date('2020-11-03'), - quantity: 25, - cost: 0.4835702479, - meterDetails: { - meterName: 'Standard Node', - unitOfMeasure: '1 /Month', - meterCategory: 'Advanced Data Security', - }, - subscriptionName: 'test-subscription', - resourceLocation: 'northeurope', - resourceGroup: 'test-resource-group', + + date: new Date('2020-11-03'), + quantity: 25, + cost: 0.4835702479, + meterDetails: { + meterName: 'Standard Node', + unitOfMeasure: '1 /Month', + meterCategory: 'Advanced Data Security', }, + subscriptionName: 'test-subscription', + resourceLocation: 'northeurope', + resourceGroup: 'test-resource-group', }, { id: 'test-subscription-id', @@ -953,20 +862,18 @@ export const mockConsumptionManagementResponseEight: UsageDetailResult[] = [ name: 'name', type: 'type', tags: {}, - properties: { - kind: 'legacy', - date: new Date('2020-11-04'), - quantity: 10, - cost: 5, - meterDetails: { - meterName: 'Geo-Replication Data transfer', - unitOfMeasure: '1 GB', - meterCategory: 'Storage', - }, - subscriptionName: 'test-subscription', - resourceLocation: 'northeurope', - resourceGroup: 'test-resource-group', + + date: new Date('2020-11-04'), + quantity: 10, + cost: 5, + meterDetails: { + meterName: 'Geo-Replication Data transfer', + unitOfMeasure: '1 GB', + meterCategory: 'Storage', }, + subscriptionName: 'test-subscription', + resourceLocation: 'northeurope', + resourceGroup: 'test-resource-group', }, { id: 'test-subscription-id', @@ -974,20 +881,18 @@ export const mockConsumptionManagementResponseEight: UsageDetailResult[] = [ name: 'name', type: 'type', tags: {}, - properties: { - kind: 'legacy', - date: new Date('2020-11-04'), - quantity: 20, - cost: 0.006280996057, - meterDetails: { - meterName: 'Data Processed', - unitOfMeasure: '100 GB', - meterCategory: 'Load Balancer', - }, - subscriptionName: 'test-subscription', - resourceLocation: 'northeurope', - resourceGroup: 'test-resource-group', + + date: new Date('2020-11-04'), + quantity: 20, + cost: 0.006280996057, + meterDetails: { + meterName: 'Data Processed', + unitOfMeasure: '100 GB', + meterCategory: 'Load Balancer', }, + subscriptionName: 'test-subscription', + resourceLocation: 'northeurope', + resourceGroup: 'test-resource-group', }, { id: 'test-subscription-id', @@ -995,20 +900,18 @@ export const mockConsumptionManagementResponseEight: UsageDetailResult[] = [ name: 'name', type: 'type', tags: {}, - properties: { - kind: 'legacy', - date: new Date('2020-11-05'), - quantity: 14.44976379, - cost: 7, - meterDetails: { - meterName: 'Memory Duration', - unitOfMeasure: '1000 GB Hours', - meterCategory: 'Container Instances', - }, - subscriptionName: 'test-subscription', - resourceLocation: 'northeurope', - resourceGroup: 'test-resource-group', + + date: new Date('2020-11-05'), + quantity: 14.44976379, + cost: 7, + meterDetails: { + meterName: 'Memory Duration', + unitOfMeasure: '1000 GB Hours', + meterCategory: 'Container Instances', }, + subscriptionName: 'test-subscription', + resourceLocation: 'northeurope', + resourceGroup: 'test-resource-group', }, { id: 'test-subscription-id', @@ -1016,20 +919,18 @@ export const mockConsumptionManagementResponseEight: UsageDetailResult[] = [ name: 'name', type: 'type', tags: {}, - properties: { - kind: 'legacy', - date: new Date('2020-11-05'), - quantity: 32, - cost: 18, - meterDetails: { - meterName: 'Memory Duration', - unitOfMeasure: '50000 GB Seconds', - meterCategory: 'Azure Databricks', - }, - subscriptionName: 'test-subscription', - resourceLocation: 'northeurope', - resourceGroup: 'test-resource-group', + + date: new Date('2020-11-05'), + quantity: 32, + cost: 18, + meterDetails: { + meterName: 'Memory Duration', + unitOfMeasure: '50000 GB Seconds', + meterCategory: 'Azure Databricks', }, + subscriptionName: 'test-subscription', + resourceLocation: 'northeurope', + resourceGroup: 'test-resource-group', }, { id: 'test-subscription-id', @@ -1037,20 +938,18 @@ export const mockConsumptionManagementResponseEight: UsageDetailResult[] = [ name: 'name', type: 'type', tags: {}, - properties: { - kind: 'legacy', - date: new Date('2020-11-06'), - quantity: 20, - cost: 0.003168316832, - meterDetails: { - meterName: 'Private Zones', - unitOfMeasure: '2', - meterCategory: 'Azure DNS', - }, - subscriptionName: 'test-subscription', - resourceLocation: 'All Regions', - resourceGroup: 'test-resource-group', + + date: new Date('2020-11-06'), + quantity: 20, + cost: 0.003168316832, + meterDetails: { + meterName: 'Private Zones', + unitOfMeasure: '2', + meterCategory: 'Azure DNS', }, + subscriptionName: 'test-subscription', + resourceLocation: 'All Regions', + resourceGroup: 'test-resource-group', }, ] @@ -1061,20 +960,18 @@ export const mockConsumptionManagementResponseNine: UsageDetailResult[] = [ name: 'name', type: 'type', tags: {}, - properties: { - kind: 'legacy', - date: new Date('2020-11-02'), - quantity: 5, - cost: 10, - meterDetails: { - meterName: 'D8 v3', - unitOfMeasure: '1 Hour', - meterCategory: 'Virtual Machines', - }, - subscriptionName: 'test-subscription', - resourceLocation: 'northeurope', - resourceGroup: 'test-resource-group', + + date: new Date('2020-11-02'), + quantity: 5, + cost: 10, + meterDetails: { + meterName: 'D8 v3', + unitOfMeasure: '1 Hour', + meterCategory: 'Virtual Machines', }, + subscriptionName: 'test-subscription', + resourceLocation: 'northeurope', + resourceGroup: 'test-resource-group', }, { id: 'test-subscription-id', @@ -1082,20 +979,18 @@ export const mockConsumptionManagementResponseNine: UsageDetailResult[] = [ name: 'name', type: 'type', tags: {}, - properties: { - kind: 'legacy', - date: new Date('2020-11-02'), - quantity: 0.250004, - cost: 15, - meterDetails: { - meterName: 'DS11-1 v2', - unitOfMeasure: '1 Hour', - meterCategory: 'Virtual Machines', - }, - subscriptionName: 'test-subscription', - resourceLocation: 'westeurope', - resourceGroup: 'test-resource-group', + + date: new Date('2020-11-02'), + quantity: 0.250004, + cost: 15, + meterDetails: { + meterName: 'DS11-1 v2', + unitOfMeasure: '1 Hour', + meterCategory: 'Virtual Machines', }, + subscriptionName: 'test-subscription', + resourceLocation: 'westeurope', + resourceGroup: 'test-resource-group', }, { id: 'test-subscription-id', @@ -1103,20 +998,18 @@ export const mockConsumptionManagementResponseNine: UsageDetailResult[] = [ name: 'name', type: 'type', tags: {}, - properties: { - kind: 'legacy', - date: new Date('2020-11-02'), - quantity: 17, - cost: 5, - meterDetails: { - meterName: 'D2 v2', - unitOfMeasure: '10 Hour', - meterCategory: 'Virtual Machines', - }, - subscriptionName: 'test-subscription', - resourceLocation: 'uksouth', - resourceGroup: 'test-resource-group', + + date: new Date('2020-11-02'), + quantity: 17, + cost: 5, + meterDetails: { + meterName: 'D2 v2', + unitOfMeasure: '10 Hour', + meterCategory: 'Virtual Machines', }, + subscriptionName: 'test-subscription', + resourceLocation: 'uksouth', + resourceGroup: 'test-resource-group', }, { id: 'test-subscription-id', @@ -1124,19 +1017,16 @@ export const mockConsumptionManagementResponseNine: UsageDetailResult[] = [ name: 'name', type: 'type', tags: {}, - properties: { - kind: 'modern', - date: new Date('2020-11-02'), - quantity: 17, - costInUSD: 5, - meterName: 'D2 v2', - unitOfMeasure: '10 Hour', - meterCategory: 'Virtual Machines', - subscriptionGuid: 'test-subscription-id', - subscriptionName: 'test-subscription', - resourceLocation: 'EASTUS', - resourceGroup: 'test-resource-group', - }, + date: new Date('2020-11-02'), + quantity: 17, + costInUSD: 5, + meterName: 'D2 v2', + unitOfMeasure: '10 Hour', + meterCategory: 'Virtual Machines', + subscriptionGuid: 'test-subscription-id', + subscriptionName: 'test-subscription', + resourceLocation: 'EASTUS', + resourceGroup: 'test-resource-group', }, ] @@ -1147,20 +1037,18 @@ export const mockConsumptionManagementResponseTen: UsageDetailResult[] = [ name: 'name', type: 'type', tags: {}, - properties: { - kind: 'legacy', - date: new Date('2020-11-01'), - quantity: 5, - cost: 10, - meterDetails: { - meterName: 'D8 v3', - unitOfMeasure: '1 Hour', - meterCategory: 'Virtual Machines', - }, - subscriptionName: 'test-subscription', - resourceLocation: 'northeurope', - resourceGroup: 'test-resource-group', + + date: new Date('2020-11-01'), + quantity: 5, + cost: 10, + meterDetails: { + meterName: 'D8 v3', + unitOfMeasure: '1 Hour', + meterCategory: 'Virtual Machines', }, + subscriptionName: 'test-subscription', + resourceLocation: 'northeurope', + resourceGroup: 'test-resource-group', }, { id: 'test-subscription-id', @@ -1168,20 +1056,18 @@ export const mockConsumptionManagementResponseTen: UsageDetailResult[] = [ name: 'name', type: 'type', tags: {}, - properties: { - kind: 'legacy', - date: new Date('2020-11-07'), - quantity: 0.250004, - cost: 15, - meterDetails: { - meterName: 'DS11-1 v2', - unitOfMeasure: '1 Hour', - meterCategory: 'Virtual Machines', - }, - subscriptionName: 'test-subscription', - resourceLocation: 'westeurope', - resourceGroup: 'test-resource-group', + + date: new Date('2020-11-07'), + quantity: 0.250004, + cost: 15, + meterDetails: { + meterName: 'DS11-1 v2', + unitOfMeasure: '1 Hour', + meterCategory: 'Virtual Machines', }, + subscriptionName: 'test-subscription', + resourceLocation: 'westeurope', + resourceGroup: 'test-resource-group', }, { id: 'test-subscription-id', @@ -1189,20 +1075,18 @@ export const mockConsumptionManagementResponseTen: UsageDetailResult[] = [ name: 'name', type: 'type', tags: {}, - properties: { - kind: 'legacy', - date: new Date('2020-11-08'), - quantity: 17, - cost: 5, - meterDetails: { - meterName: 'D2 v2', - unitOfMeasure: '10 Hour', - meterCategory: 'Virtual Machines', - }, - subscriptionName: 'test-subscription', - resourceLocation: 'uksouth', - resourceGroup: 'test-resource-group', + + date: new Date('2020-11-08'), + quantity: 17, + cost: 5, + meterDetails: { + meterName: 'D2 v2', + unitOfMeasure: '10 Hour', + meterCategory: 'Virtual Machines', }, + subscriptionName: 'test-subscription', + resourceLocation: 'uksouth', + resourceGroup: 'test-resource-group', }, ] @@ -1216,20 +1100,17 @@ export const mockConsumptionManagementResponseEleven: UsageDetailResult[] = [ custom: 'custom-tag-value', other: 'other-custom-tag-value', }, - properties: { - kind: 'legacy', - date: new Date('2020-11-02'), - quantity: 1, - cost: 10, - meterDetails: { - meterName: 'NC24', - unitOfMeasure: '10 Hours', - meterCategory: 'Virtual Machines', - }, - subscriptionName: 'test-subscription', - resourceLocation: 'northeurope', - resourceGroup: 'first-resource-group', + date: new Date('2020-11-02'), + quantity: 1, + cost: 10, + meterDetails: { + meterName: 'NC24', + unitOfMeasure: '10 Hours', + meterCategory: 'Virtual Machines', }, + subscriptionName: 'test-subscription', + resourceLocation: 'northeurope', + resourceGroup: 'first-resource-group', }, { id: 'test-subscription-id', @@ -1241,19 +1122,16 @@ export const mockConsumptionManagementResponseEleven: UsageDetailResult[] = [ 'created-by': 'created-by-tag-value', ignored: 'this-tag-should-not-be-visible', }, - properties: { - kind: 'modern', - date: new Date('2020-11-03'), - quantity: 1, - costInUSD: 10, - meterName: 'NC24', - unitOfMeasure: '10 Hours', - meterCategory: 'Virtual Machines', - subscriptionGuid: 'test-subscription-id', - subscriptionName: 'test-subscription', - resourceLocation: 'EASTUS', - resourceGroup: 'second-resource-group', - }, + date: new Date('2020-11-03'), + quantity: 1, + costInUSD: 10, + meterName: 'NC24', + unitOfMeasure: '10 Hours', + meterCategory: 'Virtual Machines', + subscriptionGuid: 'test-subscription-id', + subscriptionName: 'test-subscription', + resourceLocation: 'EASTUS', + resourceGroup: 'second-resource-group', }, { id: 'test-subscription-id', @@ -1261,18 +1139,15 @@ export const mockConsumptionManagementResponseEleven: UsageDetailResult[] = [ name: 'name', type: 'type', tags: {}, - properties: { - kind: 'modern', - date: new Date('2020-11-03'), - quantity: 1, - costInUSD: 10, - meterName: 'ND96asr A100 v4', - unitOfMeasure: '10 Hours', - meterCategory: 'Virtual Machines', - subscriptionGuid: 'test-subscription-id', - subscriptionName: 'test-subscription', - resourceLocation: 'WESTEUROPE', - resourceGroup: 'third-resource-group', - }, + date: new Date('2020-11-03'), + quantity: 1, + costInUSD: 10, + meterName: 'ND96asr A100 v4', + unitOfMeasure: '10 Hours', + meterCategory: 'Virtual Machines', + subscriptionGuid: 'test-subscription-id', + subscriptionName: 'test-subscription', + resourceLocation: 'WESTEUROPE', + resourceGroup: 'third-resource-group', }, ] diff --git a/packages/azure/src/lib/ConsumptionDetailRow.ts b/packages/azure/src/lib/ConsumptionDetailRow.ts index 37f3cd91e..0db380821 100644 --- a/packages/azure/src/lib/ConsumptionDetailRow.ts +++ b/packages/azure/src/lib/ConsumptionDetailRow.ts @@ -12,7 +12,6 @@ import { } from './VirtualMachineTypes' import { AZURE_REGIONS } from './AzureRegions' import { UsageDetailResult } from './ConsumptionTypes' -import { LegacyUsageDetail, ModernUsageDetail } from '@azure/arm-consumption' import { configLoader, Logger } from '@cloud-carbon-footprint/common' const RESOURCE_GROUP_TAG_NAME = 'resourceGroup' @@ -41,7 +40,7 @@ export default class ConsumptionDetailRow extends BillingDataRow { } if (tagNames.includes(RESOURCE_GROUP_TAG_NAME)) { - this.tags.resourceGroup = usageDetail.properties.resourceGroup + this.tags.resourceGroup = usageDetail.resourceGroup } } @@ -104,32 +103,29 @@ export default class ConsumptionDetailRow extends BillingDataRow { const getConsumptionDetails = (usageDetail: UsageDetailResult) => { const consumptionDetails: Partial = { cloudProvider: 'AZURE', - accountName: usageDetail.properties.subscriptionName, - timestamp: new Date(usageDetail.properties.date), - usageAmount: usageDetail.properties.quantity, - region: usageDetail.properties.resourceLocation, + accountName: usageDetail.subscriptionName, + timestamp: new Date(usageDetail.date), + usageAmount: usageDetail.quantity, + region: usageDetail.resourceLocation, } - let properties if (usageDetail.kind === 'modern') { - properties = usageDetail.properties as ModernUsageDetail return { ...consumptionDetails, - accountId: properties.subscriptionGuid, - usageType: properties.meterName, - usageUnit: properties.unitOfMeasure, - serviceName: properties.meterCategory, - cost: properties.costInUSD, + accountId: usageDetail.subscriptionGuid, + usageType: usageDetail.meterName, + usageUnit: usageDetail.unitOfMeasure, + serviceName: usageDetail.meterCategory, + cost: usageDetail.costInUSD, } } else { - properties = usageDetail.properties as LegacyUsageDetail return { ...consumptionDetails, accountId: usageDetail.id, - usageType: properties.meterDetails.meterName, - usageUnit: properties.meterDetails.unitOfMeasure, - serviceName: properties.meterDetails.meterCategory, - cost: properties.cost, + usageType: usageDetail.meterDetails.meterName, + usageUnit: usageDetail.meterDetails.unitOfMeasure, + serviceName: usageDetail.meterDetails.meterCategory, + cost: usageDetail.cost, } } } diff --git a/packages/azure/src/lib/ConsumptionManagement.ts b/packages/azure/src/lib/ConsumptionManagement.ts index a1d807278..eb9cb0517 100644 --- a/packages/azure/src/lib/ConsumptionManagement.ts +++ b/packages/azure/src/lib/ConsumptionManagement.ts @@ -114,8 +114,8 @@ export default class ConsumptionManagementService { usageRows .filter( (consumptionRow) => - new Date(consumptionRow.properties.date) >= startDate && - new Date(consumptionRow.properties.date) <= endDate, + new Date(consumptionRow.date) >= startDate && + new Date(consumptionRow.date) <= endDate, ) .map((consumptionRow) => { const consumptionDetailRow: ConsumptionDetailRow = @@ -170,16 +170,13 @@ export default class ConsumptionManagementService { type: '', tags: {}, kind: 'legacy' as const, - properties: { - kind: 'legacy' as const, - date: new Date(''), - quantity: 1, - cost: 1, - meterDetails: { - meterName: inputDataRow.usageType, - unitOfMeasure: inputDataRow.usageUnit, - meterCategory: inputDataRow.serviceName, - }, + date: new Date(''), + quantity: 1, + cost: 1, + meterDetails: { + meterName: inputDataRow.usageType, + unitOfMeasure: inputDataRow.usageUnit, + meterCategory: inputDataRow.serviceName, subscriptionName: '', resourceLocation: inputDataRow.region, }, diff --git a/packages/azure/src/lib/ConsumptionTypes.ts b/packages/azure/src/lib/ConsumptionTypes.ts index 7452535af..df1c2f2ac 100644 --- a/packages/azure/src/lib/ConsumptionTypes.ts +++ b/packages/azure/src/lib/ConsumptionTypes.ts @@ -23,13 +23,8 @@ export type UsageRowPageErrorResponse = { message: string } -export type UsageDetailResult = { - id: string - name: string - type: string +export type UsageDetailResult = (LegacyUsageDetail | ModernUsageDetail) & { tags: TagCollection - kind: string - properties: LegacyUsageDetail | ModernUsageDetail } export const UNKNOWN_SERVICES: string[] = [ diff --git a/packages/cli/package.json b/packages/cli/package.json index f836a1b52..0dcaab347 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -68,8 +68,8 @@ "typescript": "^4.6.2" }, "dependencies": { - "@azure/arm-consumption": "^9.1.0", - "@azure/arm-resources-subscriptions": "^2.0.1", + "@azure/arm-consumption": "^9.2.0", + "@azure/arm-resources-subscriptions": "^2.0.2", "@cloud-carbon-footprint/app": "1.2.0", "@cloud-carbon-footprint/common": "^1.10.0", "@types/cli-table": "^0.3.0", diff --git a/packages/cli/src/__tests__/CreateLookupTable/__snapshots__/createLookupTable.test.ts.snap b/packages/cli/src/__tests__/CreateLookupTable/__snapshots__/createLookupTable.test.ts.snap index d9b13440e..aa220dfc6 100644 --- a/packages/cli/src/__tests__/CreateLookupTable/__snapshots__/createLookupTable.test.ts.snap +++ b/packages/cli/src/__tests__/CreateLookupTable/__snapshots__/createLookupTable.test.ts.snap @@ -22,10 +22,10 @@ AmazonRDS,us-east-1,Aurora:StorageIOUsage,,,0,0 exports[`createLookupTable creates lookup table CSV creates Azure lookup table CSV file, with default output file path 1`] = ` "serviceName,region,usageType,vCpus,machineType,kilowattHours,co2e -Virtual Machines,uksouth,D2 v2/DS2 v2,,,0.015380813559107052,0.0000034606830507990865 -Virtual Machines,westeurope,D4as v4 Spot,,,0.011452157391968623,0.000003760888487522496 -Azure App Service,CentralUS,F1,,,0.00976766510007844,0.000004163506319568836 -Container Instances,SouthCentralUS,vCPU Duration,,,0.0025359,9.464764929e-7 +Virtual Machines,uksouth,D2 v2/DS2 v2,,,0.011945378995278953,0.000004196172273564499 +Virtual Machines,westeurope,D4as v4 Spot,,,0.011360154945601171,0.000003990594791924814 +Azure App Service,CentralUS,F1,,,0.011230847309593953,0.000003945171610526542 +Container Instances,SouthCentralUS,vCPU Duration,,,0.0025359,8.908108543678499e-7 Azure Database for MySQL,Unknown,2 vCore,,,0.015215399999999999,0.000005344865126207099 VPN Gateway,Unknown,VpnGw1,,,0.015215399999999999,0.000005344865126207099 " @@ -33,10 +33,10 @@ VPN Gateway,Unknown,VpnGw1,,,0.015215399999999999,0.000005344865126207099 exports[`createLookupTable creates lookup table CSV creates Azure lookup table CSV file, with provided output file name 1`] = ` "serviceName,region,usageType,vCpus,machineType,kilowattHours,co2e -Virtual Machines,uksouth,D2 v2/DS2 v2,,,0.015380813559107052,0.0000034606830507990865 -Virtual Machines,westeurope,D4as v4 Spot,,,0.011452157391968623,0.000003760888487522496 -Azure App Service,CentralUS,F1,,,0.00976766510007844,0.000004163506319568836 -Container Instances,SouthCentralUS,vCPU Duration,,,0.0025359,9.464764929e-7 +Virtual Machines,uksouth,D2 v2/DS2 v2,,,0.011945378995278953,0.000004196172273564499 +Virtual Machines,westeurope,D4as v4 Spot,,,0.011360154945601171,0.000003990594791924814 +Azure App Service,CentralUS,F1,,,0.011230847309593953,0.000003945171610526542 +Container Instances,SouthCentralUS,vCPU Duration,,,0.0025359,8.908108543678499e-7 Azure Database for MySQL,Unknown,2 vCore,,,0.015215399999999999,0.000005344865126207099 VPN Gateway,Unknown,VpnGw1,,,0.015215399999999999,0.000005344865126207099 " diff --git a/yarn.lock b/yarn.lock index c158810ec..279bb9626 100644 --- a/yarn.lock +++ b/yarn.lock @@ -34,7 +34,7 @@ __metadata: languageName: node linkType: hard -"@azure/arm-advisor@npm:^3.0.3": +"@azure/arm-advisor@npm:^3.1.0": version: 3.1.0 resolution: "@azure/arm-advisor@npm:3.1.0" dependencies: @@ -47,29 +47,29 @@ __metadata: languageName: node linkType: hard -"@azure/arm-consumption@npm:9.1.0": - version: 9.1.0 - resolution: "@azure/arm-consumption@npm:9.1.0" +"@azure/arm-consumption@npm:^9.2.0": + version: 9.2.0 + resolution: "@azure/arm-consumption@npm:9.2.0" dependencies: "@azure/core-auth": ^1.3.0 - "@azure/core-client": ^1.5.0 + "@azure/core-client": ^1.6.1 "@azure/core-paging": ^1.2.0 "@azure/core-rest-pipeline": ^1.8.0 tslib: ^2.2.0 - checksum: 4e8f8f61433e3c031c1072a627375c8e5f183c78e0ad623e00f0f04013e5a535f5dcdc7c042212871032913053dbda572b7f8982c2940cbccba7ba49166d05c5 + checksum: c37cab95d06a3462725609cdc1e32ea95540f865f2cdf67df7841819b37a3eee2bbe9a9eaed2267b371b3d92ee1090b1fbf356f22798392440bc2141e2c4ae36 languageName: node linkType: hard -"@azure/arm-resources-subscriptions@npm:^2.0.1": - version: 2.0.1 - resolution: "@azure/arm-resources-subscriptions@npm:2.0.1" +"@azure/arm-resources-subscriptions@npm:^2.0.2": + version: 2.0.2 + resolution: "@azure/arm-resources-subscriptions@npm:2.0.2" dependencies: "@azure/core-auth": ^1.3.0 - "@azure/core-client": ^1.5.0 + "@azure/core-client": ^1.6.1 "@azure/core-paging": ^1.2.0 "@azure/core-rest-pipeline": ^1.8.0 tslib: ^2.2.0 - checksum: b39332c922ca7638df0e6a865ba50950a43c130185c8078d9d3434dbfa44ca7c8068d8570eb20d86db73674b68c8f108c3b0cf5aeb79859cfb348fdf1cf577b1 + checksum: b9971dbfbae8b418a3a1f0200a8e503ac9a9651cf60a28eef83673a67529ef3af0653a3bee6da6e3a7548aa9e8e48a1818f79460dc5e9044914227093a108fd9 languageName: node linkType: hard @@ -83,18 +83,28 @@ __metadata: languageName: node linkType: hard -"@azure/core-client@npm:1.6.0": - version: 1.6.0 - resolution: "@azure/core-client@npm:1.6.0" +"@azure/core-auth@npm:^1.4.0": + version: 1.4.0 + resolution: "@azure/core-auth@npm:1.4.0" dependencies: "@azure/abort-controller": ^1.0.0 - "@azure/core-auth": ^1.3.0 - "@azure/core-rest-pipeline": ^1.5.0 + tslib: ^2.2.0 + checksum: 1c76c296fe911ad39fc780b033c25a92c41c5a15f011b816d42c13584f627a4dd153dfb4334900ec93eb5b006e14bdda37e8412a7697c5a9636a0abaccffad39 + languageName: node + linkType: hard + +"@azure/core-client@npm:^1.4.0, @azure/core-client@npm:^1.6.1": + version: 1.7.2 + resolution: "@azure/core-client@npm:1.7.2" + dependencies: + "@azure/abort-controller": ^1.0.0 + "@azure/core-auth": ^1.4.0 + "@azure/core-rest-pipeline": ^1.9.1 "@azure/core-tracing": ^1.0.0 "@azure/core-util": ^1.0.0 "@azure/logger": ^1.0.0 tslib: ^2.2.0 - checksum: 5f9c0d48d03f382ac04e34dbfbcd846f829f5df668b8d357927da75d2c8e4a06460f6e51a843abea0ed39e0c0d4a53a1dfe6134c7dadd14c5f96ece601a44ea2 + checksum: 2c6af345ed7f97d7d214873cf08c2564603c659abc8950a1b866b137dfb8b68cc367f75a1c0d0ea840ac8f66a50604661f6f2569184dcda717ec4e62c3c6d977 languageName: node linkType: hard @@ -107,7 +117,7 @@ __metadata: languageName: node linkType: hard -"@azure/core-rest-pipeline@npm:^1.1.0, @azure/core-rest-pipeline@npm:^1.5.0, @azure/core-rest-pipeline@npm:^1.8.0": +"@azure/core-rest-pipeline@npm:^1.1.0, @azure/core-rest-pipeline@npm:^1.8.0": version: 1.9.0 resolution: "@azure/core-rest-pipeline@npm:1.9.0" dependencies: @@ -125,6 +135,23 @@ __metadata: languageName: node linkType: hard +"@azure/core-rest-pipeline@npm:^1.9.1": + version: 1.10.3 + resolution: "@azure/core-rest-pipeline@npm:1.10.3" + dependencies: + "@azure/abort-controller": ^1.0.0 + "@azure/core-auth": ^1.4.0 + "@azure/core-tracing": ^1.0.1 + "@azure/core-util": ^1.3.0 + "@azure/logger": ^1.0.0 + form-data: ^4.0.0 + http-proxy-agent: ^5.0.0 + https-proxy-agent: ^5.0.0 + tslib: ^2.2.0 + checksum: 6704101a2d6ca651ffc09e4d9fa77bc9718ed0d9b96b93d23a845dbac50a71bb089486808230ebaf59c9cb2557661ee5b020222ad5ad6d49d09b47c6ed11fce4 + languageName: node + linkType: hard + "@azure/core-tracing@npm:^1.0.0, @azure/core-tracing@npm:^1.0.1": version: 1.0.1 resolution: "@azure/core-tracing@npm:1.0.1" @@ -143,9 +170,19 @@ __metadata: languageName: node linkType: hard -"@azure/identity@npm:^3.0.0": - version: 3.0.0 - resolution: "@azure/identity@npm:3.0.0" +"@azure/core-util@npm:^1.3.0": + version: 1.3.1 + resolution: "@azure/core-util@npm:1.3.1" + dependencies: + "@azure/abort-controller": ^1.0.0 + tslib: ^2.2.0 + checksum: 861d9bd1a9db863bfc71b95447d548f100bec975670efb658ddc9b7c0d676d03a21a48042aa9ec853ad6b1bd38da2cf3069cd7e872196bea1598bf4357430d8f + languageName: node + linkType: hard + +"@azure/identity@npm:^3.1.4": + version: 3.1.4 + resolution: "@azure/identity@npm:3.1.4" dependencies: "@azure/abort-controller": ^1.0.0 "@azure/core-auth": ^1.3.0 @@ -154,16 +191,16 @@ __metadata: "@azure/core-tracing": ^1.0.0 "@azure/core-util": ^1.0.0 "@azure/logger": ^1.0.0 - "@azure/msal-browser": ^2.26.0 - "@azure/msal-common": ^7.0.0 - "@azure/msal-node": ^1.10.0 + "@azure/msal-browser": ^2.32.2 + "@azure/msal-common": ^9.0.2 + "@azure/msal-node": ^1.14.6 events: ^3.0.0 jws: ^4.0.0 open: ^8.0.0 stoppable: ^1.1.0 tslib: ^2.2.0 uuid: ^8.3.0 - checksum: 1af675d6082d86d6325aa4db5e1e75aa730ec5f39e490e94236dc043908191bac9ccbaa0a7491a41678d07101082d0de0b7f415131d471ba47b1fef0b608fc1b + checksum: 8851b32623565235ca390e8db340a7320864267c552a44d5e564db126d0dc7020b7de787914e50578b720ed80b99c87e957dfe302400cd50d64367e33ee294a0 languageName: node linkType: hard @@ -200,9 +237,9 @@ __metadata: languageName: node linkType: hard -"@azure/ms-rest-js@npm:^2.6.2": - version: 2.6.2 - resolution: "@azure/ms-rest-js@npm:2.6.2" +"@azure/ms-rest-js@npm:^2.6.6": + version: 2.6.6 + resolution: "@azure/ms-rest-js@npm:2.6.6" dependencies: "@azure/core-auth": ^1.1.4 abort-controller: ^3.0.0 @@ -212,8 +249,8 @@ __metadata: tslib: ^1.10.0 tunnel: 0.0.6 uuid: ^8.3.2 - xml2js: ^0.4.19 - checksum: de650dfc3928a2036923fb3bf50e0324f9f36f5d320ca6d128005b1345ad80b39ab3410073ac35fb137dec4c7d799c602b1cafdf9573b3432b6b8eee8a1e39f8 + xml2js: ^0.5.0 + checksum: bd85d2df8da800c96c062a9ce26e98f72ee6fa61c9e010ec45fc3f63f9276a2668737a73dd2e19729dcfb81f22eedc7919262cc24ae5581ab4f4b526a5fcb6c1 languageName: node linkType: hard @@ -228,30 +265,37 @@ __metadata: languageName: node linkType: hard -"@azure/msal-browser@npm:^2.26.0": - version: 2.30.0 - resolution: "@azure/msal-browser@npm:2.30.0" +"@azure/msal-browser@npm:^2.32.2": + version: 2.36.0 + resolution: "@azure/msal-browser@npm:2.36.0" dependencies: - "@azure/msal-common": ^7.6.0 - checksum: 571722fb4a774a7f069459c1d3af8e886f7660fa384f46a8d2eaeda98fb8eab427907f159d62dc01f5128844484bfb4401a63a9c3f7634f59f3d4e3194cc80ac + "@azure/msal-common": ^12.1.0 + checksum: bfb6fdad074ae8b426bbae8713bebef9a28b2c447ce87bd50cee051c85c026a5568e235b1536c6a9cd3bb0abd14e46572b0af487f4a4c814b4f3e3787c45bda0 languageName: node linkType: hard -"@azure/msal-common@npm:^7.0.0, @azure/msal-common@npm:^7.6.0": - version: 7.6.0 - resolution: "@azure/msal-common@npm:7.6.0" - checksum: b2541603c87778daad4627ddd796577651ccae18c93d518f4c0e40e42475dab8781644903ad1c7f876277e0c436d47548c82d9034b458bca00c746fd465ae184 +"@azure/msal-common@npm:^12.1.0": + version: 12.1.0 + resolution: "@azure/msal-common@npm:12.1.0" + checksum: 6ed6fa9d8303906617ad1f1d0e4805e297c5ca6fa72ff5095febed99daf5e0a5e9b59b6ec3baa62d996eb6356ecf8d104633a3c6a6414759c05e58e3fa82399b languageName: node linkType: hard -"@azure/msal-node@npm:^1.10.0": - version: 1.14.2 - resolution: "@azure/msal-node@npm:1.14.2" +"@azure/msal-common@npm:^9.0.2": + version: 9.1.1 + resolution: "@azure/msal-common@npm:9.1.1" + checksum: d51d645ee485b699a0fbeab6d9bb01c91f4124704f756266e109c2d1d8ea0ca8d66a9ab81f62f9f76e7358906badcf7d2dd9911c18e20f57770ec9beb4fc4fcd + languageName: node + linkType: hard + +"@azure/msal-node@npm:^1.14.6": + version: 1.17.1 + resolution: "@azure/msal-node@npm:1.17.1" dependencies: - "@azure/msal-common": ^7.6.0 - jsonwebtoken: ^8.5.1 + "@azure/msal-common": ^12.1.0 + jsonwebtoken: ^9.0.0 uuid: ^8.3.0 - checksum: 5becc4cce60957c619b1b709f89fae5d56586e13487979474c5e50554bfa56c58edae64d88ac6141b86ccd0ebb38d813323d62bacc0703c275a1b8e5ee62b128 + checksum: cbc06cbaa4a2cc36d34bf90460321458b903d8a7f909f34626037e7c3faf8e9562020c4fc7dcf99d461e447a655a47ba25af32d1ce4f1911d4c6ec1712903d22 languageName: node linkType: hard @@ -2263,11 +2307,11 @@ __metadata: version: 0.0.0-use.local resolution: "@cloud-carbon-footprint/azure@workspace:packages/azure" dependencies: - "@azure/arm-advisor": ^3.0.3 - "@azure/arm-consumption": ^9.1.0 - "@azure/arm-resources-subscriptions": ^2.0.1 - "@azure/identity": ^3.0.0 - "@azure/ms-rest-js": ^2.6.2 + "@azure/arm-advisor": ^3.1.0 + "@azure/arm-consumption": ^9.2.0 + "@azure/arm-resources-subscriptions": ^2.0.2 + "@azure/identity": ^3.1.4 + "@azure/ms-rest-js": ^2.6.6 "@azure/ms-rest-nodeauth": ^3.1.1 "@cloud-carbon-footprint/common": ^1.10.0 "@cloud-carbon-footprint/core": ^0.17.1 @@ -2301,8 +2345,8 @@ __metadata: version: 0.0.0-use.local resolution: "@cloud-carbon-footprint/cli@workspace:packages/cli" dependencies: - "@azure/arm-consumption": ^9.1.0 - "@azure/arm-resources-subscriptions": ^2.0.1 + "@azure/arm-consumption": ^9.2.0 + "@azure/arm-resources-subscriptions": ^2.0.2 "@cloud-carbon-footprint/app": 1.2.0 "@cloud-carbon-footprint/common": ^1.10.0 "@types/cli-table": ^0.3.0 @@ -14804,6 +14848,18 @@ __metadata: languageName: node linkType: hard +"jsonwebtoken@npm:^9.0.0": + version: 9.0.0 + resolution: "jsonwebtoken@npm:9.0.0" + dependencies: + jws: ^3.2.2 + lodash: ^4.17.21 + ms: ^2.1.1 + semver: ^7.3.8 + checksum: b9181cecf9df99f1dc0253f91ba000a1aa4d91f5816d1608c0dba61a5623726a0bfe200b51df25de18c1a6000825d231ad7ce2788aa54fd48dcb760ad9eb9514 + languageName: node + linkType: hard + "jsprim@npm:^1.2.2": version: 1.4.2 resolution: "jsprim@npm:1.4.2" @@ -20267,6 +20323,17 @@ __metadata: languageName: node linkType: hard +"semver@npm:^7.3.8": + version: 7.5.0 + resolution: "semver@npm:7.5.0" + dependencies: + lru-cache: ^6.0.0 + bin: + semver: bin/semver.js + checksum: 2d266937756689a76f124ffb4c1ea3e1bbb2b263219f90ada8a11aebebe1280b13bb76cca2ca96bdee3dbc554cbc0b24752eb895b2a51577aa644427e9229f2b + languageName: node + linkType: hard + "send@npm:0.18.0": version: 0.18.0 resolution: "send@npm:0.18.0" @@ -23778,6 +23845,16 @@ __metadata: languageName: node linkType: hard +"xml2js@npm:^0.5.0": + version: 0.5.0 + resolution: "xml2js@npm:0.5.0" + dependencies: + sax: ">=0.6.0" + xmlbuilder: ~11.0.0 + checksum: 1aa71d62e5bc2d89138e3929b9ea46459157727759cbc62ef99484b778641c0cd21fb637696c052d901a22f82d092a3e740a16b4ce218e81ac59b933535124ea + languageName: node + linkType: hard + "xmlbuilder@npm:~11.0.0": version: 11.0.1 resolution: "xmlbuilder@npm:11.0.1" From 180ab62e3cc08dec6e8c2f237eca45c81fbb85fd Mon Sep 17 00:00:00 2001 From: Arik Smith Date: Mon, 1 May 2023 21:25:49 -0400 Subject: [PATCH 049/251] add additional azure region aliases --- packages/azure/src/lib/AzureRegions.ts | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/packages/azure/src/lib/AzureRegions.ts b/packages/azure/src/lib/AzureRegions.ts index 8876bafa1..3b3538d51 100644 --- a/packages/azure/src/lib/AzureRegions.ts +++ b/packages/azure/src/lib/AzureRegions.ts @@ -31,15 +31,21 @@ export const AZURE_REGIONS = { name: 'australiasoutheast', options: ['australiasoutheast'], }, - AP_EAST: { name: 'apeast', options: ['apeast'] }, - AP_SOUTH_EAST: { name: 'apsoutheast', options: ['apsoutheast'] }, - AP_JAPAN_EAST: { name: 'japaneast', options: ['japaneast'] }, + AP_EAST: { name: 'apeast', options: ['apeast', 'AP East'] }, + AP_SOUTH_EAST: { + name: 'apsoutheast', + options: ['apsoutheast', 'AP Southeast'], + }, + AP_JAPAN_EAST: { name: 'japaneast', options: ['japaneast', 'JA East'] }, AP_JAPAN_WEST: { name: 'japanwest', options: ['japanwest'] }, AP_JAPAN: { name: 'japan', options: ['japan'] }, AP_KOREA: { name: 'korea', options: ['korea'] }, - AP_KOREA_EAST: { name: 'koreacentral', options: ['koreacentral'] }, + AP_KOREA_EAST: { + name: 'koreacentral', + options: ['koreacentral', 'KR Central'], + }, AP_KOREA_SOUTH: { name: 'koreasouth', options: ['koreasouth'] }, - ASIA: { name: 'asia', options: ['asia'] }, + ASIA: { name: 'asia', options: ['asia', 'Asia'] }, ASIA_PACIFIC: { name: 'asiapacific', options: ['asiapacific'] }, ASIA_EAST: { name: 'eastasia', options: ['eastasia', 'asiaeast'] }, ASIA_EAST_STAGE: { name: 'eastasiastage', options: ['eastasiastage'] }, @@ -57,7 +63,7 @@ export const AZURE_REGIONS = { }, IN_CENTRAL: { name: 'centralindia', - options: ['centralindia', 'IndiaCentral'], + options: ['centralindia', 'IndiaCentral', 'IN Central'], }, IN_JIO_CENTRAL: { name: 'jioindiacentral', options: ['jioindiacentral'] }, IN_JIO_WEST: { name: 'jioindiawest', options: ['jioindiawest'] }, From a0f84979bb1e89cc8d4726ecbcc82e2eb2986356 Mon Sep 17 00:00:00 2001 From: Arik Smith Date: Mon, 1 May 2023 21:33:50 -0400 Subject: [PATCH 050/251] add additional azure region aliases --- packages/azure/src/lib/AzureRegions.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/azure/src/lib/AzureRegions.ts b/packages/azure/src/lib/AzureRegions.ts index 3b3538d51..8f24c09c6 100644 --- a/packages/azure/src/lib/AzureRegions.ts +++ b/packages/azure/src/lib/AzureRegions.ts @@ -37,7 +37,7 @@ export const AZURE_REGIONS = { options: ['apsoutheast', 'AP Southeast'], }, AP_JAPAN_EAST: { name: 'japaneast', options: ['japaneast', 'JA East'] }, - AP_JAPAN_WEST: { name: 'japanwest', options: ['japanwest'] }, + AP_JAPAN_WEST: { name: 'japanwest', options: ['japanwest', 'JA West'] }, AP_JAPAN: { name: 'japan', options: ['japan'] }, AP_KOREA: { name: 'korea', options: ['korea'] }, AP_KOREA_EAST: { @@ -143,7 +143,7 @@ export const AZURE_REGIONS = { US_US_EAP: { name: 'unitedstateseuap', options: ['unitedstateseuap'] }, US_WEST_CENTRAL: { name: 'WestCentralUS', - options: ['WestCentralUS', 'westcentralus'], + options: ['WestCentralUS', 'westcentralus', 'US West Central'], }, US_NORTH: { name: 'USNorth', options: ['USNorth'] }, US_WEST: { name: 'WestUS', options: ['WestUS', 'westus', 'US West'] }, From 36672ec003d7870d4b85792e8ebcf863b18c0f9b Mon Sep 17 00:00:00 2001 From: Arik Smith Date: Tue, 2 May 2023 09:45:34 -0400 Subject: [PATCH 051/251] add concurrency cancel and trigger back to demo app --- .github/workflows/demo-app.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/demo-app.yml b/.github/workflows/demo-app.yml index 868ce99a0..9c70653df 100644 --- a/.github/workflows/demo-app.yml +++ b/.github/workflows/demo-app.yml @@ -13,12 +13,12 @@ on: paths-ignore: - 'microsite/**' - '.github/workflows/deploy-microsite.yml' -# concurrency: -# group: ${{ github.workflow }} - # cancel-in-progress: true + concurrency: + group: ${{ github.workflow }} + cancel-in-progress: true jobs: deploy-demo-app: - # if: github.event.pull_request.head.ref == 'changeset-release/trunk' + if: github.event.pull_request.head.ref == 'changeset-release/trunk' runs-on: ubuntu-latest container: image: node:16.19-alpine3.17 From 5b5a0ac563280e7e78e71c1ee131ac126302353e Mon Sep 17 00:00:00 2001 From: Arik Smith Date: Tue, 2 May 2023 09:47:57 -0400 Subject: [PATCH 052/251] changeset: Updates support to version 9.2 of the @azure/arm-consumption package --- .changeset/lazy-bags-repeat.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/lazy-bags-repeat.md diff --git a/.changeset/lazy-bags-repeat.md b/.changeset/lazy-bags-repeat.md new file mode 100644 index 000000000..2122a345a --- /dev/null +++ b/.changeset/lazy-bags-repeat.md @@ -0,0 +1,5 @@ +--- +'@cloud-carbon-footprint/azure': minor +--- + +Updates support to version 9.2 of the @azure/arm-consumption package From b82eacbad50d5d6186b648b63dc5c6cb2456c31f Mon Sep 17 00:00:00 2001 From: Arik Smith Date: Tue, 2 May 2023 09:49:00 -0400 Subject: [PATCH 053/251] [#1127] removes version lock from create-app --- packages/create-app/templates/default-app/package.json | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/create-app/templates/default-app/package.json b/packages/create-app/templates/default-app/package.json index f73d70cb3..95dc5a431 100644 --- a/packages/create-app/templates/default-app/package.json +++ b/packages/create-app/templates/default-app/package.json @@ -20,9 +20,7 @@ "@grpc/grpc-js": "^1.4.6", "@types/eslint": "^8.2.1", "google-auth-library": "^7.11.0", - "@babel/core": "~7.16.7", - "@azure/arm-consumption": "9.1.0", - "@azure/core-client": "1.6.0" + "@babel/core": "~7.16.7" }, "workspaces": { "packages": [ From 5e8974851946f8950c6a2613e6f9df78dac83bc4 Mon Sep 17 00:00:00 2001 From: Arik Smith Date: Tue, 2 May 2023 09:49:58 -0400 Subject: [PATCH 054/251] changeset: Removes version locks for azure sdk --- .changeset/giant-elephants-listen.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/giant-elephants-listen.md diff --git a/.changeset/giant-elephants-listen.md b/.changeset/giant-elephants-listen.md new file mode 100644 index 000000000..afb89ee9a --- /dev/null +++ b/.changeset/giant-elephants-listen.md @@ -0,0 +1,5 @@ +--- +'@cloud-carbon-footprint/create-app': patch +--- + +Removes version locks for azure sdk From af2c645f78094b6437c565ced362ebe277b30590 Mon Sep 17 00:00:00 2001 From: Cam Casher Date: Tue, 2 May 2023 15:50:31 -0600 Subject: [PATCH 055/251] removes redundant mock data script file --- scripts/updateMockData.js | 41 --------------------------------------- 1 file changed, 41 deletions(-) delete mode 100644 scripts/updateMockData.js diff --git a/scripts/updateMockData.js b/scripts/updateMockData.js deleted file mode 100644 index c61c091e4..000000000 --- a/scripts/updateMockData.js +++ /dev/null @@ -1,41 +0,0 @@ -const fs = require('fs'); -const path = require('path'); - -async function main() { - const data = fs.readFileSync( - path.resolve(__dirname, `../packages/client/stub-server/mockData.json`), - 'utf8', - ); - const mockData = JSON.parse(data); - const today = new Date(); - - mockData.footprint.forEach((footprint, index) => { - footprint.timestamp = new Date(today.getFullYear(), today.getMonth() - 15 - index, today.getDate()); - footprint.serviceEstimates.forEach((serviceEstimate) => { - const emissions = mockData.emissions.find(({ region }) => region === serviceEstimate.region); - const co2e = emissions ? serviceEstimate.kilowattHours * emissions.mtPerKwHour : serviceEstimate.co2e; - serviceEstimate.co2e = co2e; - }) - }) - - mockData.recommendations.forEach((recommendation) => { - const emissions = mockData.emissions.find(({ region }) => region === recommendation.region); - const co2eSavings = emissions ? recommendation.kilowattHourSavings * emissions.mtPerKwHour : recommendation.co2eSavings; - recommendation.co2eSavings = co2eSavings; - }) - - fs.writeFileSync( - path.resolve(__dirname, `../packages/client/stub-server/mockData.json`), - JSON.stringify(mockData), - (err) => { - if (err) { - console.error(err) - } - }, - ) -} - -main().catch((error) => { - console.error(error.stack) - process.exit(1) -}); \ No newline at end of file From 903f3a81562541ed4b8f658d8474ff52bd195788 Mon Sep 17 00:00:00 2001 From: Arik Smith Date: Wed, 3 May 2023 17:34:00 +0000 Subject: [PATCH 056/251] moves google-auth import above ccf imports --- packages/gcp/src/application/GCPAccount.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/gcp/src/application/GCPAccount.ts b/packages/gcp/src/application/GCPAccount.ts index e7e66ef59..594d0d2c3 100644 --- a/packages/gcp/src/application/GCPAccount.ts +++ b/packages/gcp/src/application/GCPAccount.ts @@ -14,6 +14,7 @@ import { ImagesClient, MachineTypesClient, } from '@google-cloud/compute' +import { GoogleAuth } from 'google-auth-library' import { ICloudService, Region, @@ -34,7 +35,6 @@ import { LookupTableOutput, GroupBy, } from '@cloud-carbon-footprint/common' -import { GoogleAuth } from 'google-auth-library' import ServiceWrapper from '../lib/ServiceWrapper' import { BillingExportTable, ComputeEngine, Recommendations } from '../lib' import { GCP_CLOUD_CONSTANTS, getGCPEmissionsFactors } from '../domain' From 1650d9b4ab828c7ae21222c87553fd5ef6a648b2 Mon Sep 17 00:00:00 2001 From: Arik Smith Date: Wed, 3 May 2023 17:53:56 +0000 Subject: [PATCH 057/251] update lock file --- yarn.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/yarn.lock b/yarn.lock index 0aeb9b378..63f802555 100644 --- a/yarn.lock +++ b/yarn.lock @@ -20671,7 +20671,7 @@ __metadata: languageName: node linkType: hard -"semver@npm:^7.3.8": +"semver@npm:^7.1.2, semver@npm:^7.3.8": version: 7.5.0 resolution: "semver@npm:7.5.0" dependencies: From 31158ffbb38576050b36cac6a703b11bfcf01986 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 3 May 2023 18:03:43 +0000 Subject: [PATCH 058/251] Version Packages --- .changeset/brave-mugs-serve.md | 5 ----- .changeset/dry-tomatoes-breathe.md | 5 ----- .changeset/forty-lamps-fry.md | 8 -------- .changeset/giant-elephants-listen.md | 5 ----- .changeset/good-news-chew.md | 5 ----- .changeset/lazy-bags-repeat.md | 5 ----- .changeset/moody-emus-wash.md | 5 ----- .changeset/proud-humans-work.md | 10 ---------- .changeset/shaggy-melons-exercise.md | 5 ----- packages/api/CHANGELOG.md | 8 ++++++++ packages/api/package.json | 4 ++-- packages/app/CHANGELOG.md | 16 +++++++++++++++ packages/app/package.json | 8 ++++---- packages/aws/CHANGELOG.md | 9 +++++++++ packages/aws/package.json | 4 ++-- packages/azure/CHANGELOG.md | 12 ++++++++++++ packages/azure/package.json | 4 ++-- packages/cli/CHANGELOG.md | 8 ++++++++ packages/cli/package.json | 4 ++-- packages/client/CHANGELOG.md | 7 +++++++ packages/client/package.json | 2 +- packages/core/CHANGELOG.md | 6 ++++++ packages/core/package.json | 2 +- packages/create-app/CHANGELOG.md | 8 ++++++++ packages/create-app/package.json | 2 +- packages/gcp/CHANGELOG.md | 13 +++++++++++++ packages/gcp/package.json | 4 ++-- packages/integration-tests/CHANGELOG.md | 6 ++++++ packages/integration-tests/package.json | 2 +- yarn.lock | 26 ++++++++++++------------- 30 files changed, 124 insertions(+), 84 deletions(-) delete mode 100644 .changeset/brave-mugs-serve.md delete mode 100644 .changeset/dry-tomatoes-breathe.md delete mode 100644 .changeset/forty-lamps-fry.md delete mode 100644 .changeset/giant-elephants-listen.md delete mode 100644 .changeset/good-news-chew.md delete mode 100644 .changeset/lazy-bags-repeat.md delete mode 100644 .changeset/moody-emus-wash.md delete mode 100644 .changeset/proud-humans-work.md delete mode 100644 .changeset/shaggy-melons-exercise.md diff --git a/.changeset/brave-mugs-serve.md b/.changeset/brave-mugs-serve.md deleted file mode 100644 index 5ee531f09..000000000 --- a/.changeset/brave-mugs-serve.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@cloud-carbon-footprint/gcp': minor ---- - -Replaces the use of googleapis with separate package clients diff --git a/.changeset/dry-tomatoes-breathe.md b/.changeset/dry-tomatoes-breathe.md deleted file mode 100644 index 8e328c40d..000000000 --- a/.changeset/dry-tomatoes-breathe.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@cloud-carbon-footprint/aws': patch ---- - -Adds support for additional usage types diff --git a/.changeset/forty-lamps-fry.md b/.changeset/forty-lamps-fry.md deleted file mode 100644 index 8cb499b56..000000000 --- a/.changeset/forty-lamps-fry.md +++ /dev/null @@ -1,8 +0,0 @@ ---- -'@cloud-carbon-footprint/api': patch -'@cloud-carbon-footprint/client': patch -'@cloud-carbon-footprint/create-app': patch -'@cloud-carbon-footprint/integration-tests': patch ---- - -fixes mock data and integration tests diff --git a/.changeset/giant-elephants-listen.md b/.changeset/giant-elephants-listen.md deleted file mode 100644 index afb89ee9a..000000000 --- a/.changeset/giant-elephants-listen.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@cloud-carbon-footprint/create-app': patch ---- - -Removes version locks for azure sdk diff --git a/.changeset/good-news-chew.md b/.changeset/good-news-chew.md deleted file mode 100644 index 234f54472..000000000 --- a/.changeset/good-news-chew.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@cloud-carbon-footprint/gcp': minor ---- - -Adds support for including tags and labels in queries via config diff --git a/.changeset/lazy-bags-repeat.md b/.changeset/lazy-bags-repeat.md deleted file mode 100644 index 2122a345a..000000000 --- a/.changeset/lazy-bags-repeat.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@cloud-carbon-footprint/azure': minor ---- - -Updates support to version 9.2 of the @azure/arm-consumption package diff --git a/.changeset/moody-emus-wash.md b/.changeset/moody-emus-wash.md deleted file mode 100644 index 8ce4b02e5..000000000 --- a/.changeset/moody-emus-wash.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@cloud-carbon-footprint/gcp': patch ---- - -adds DCU to gcp compute types diff --git a/.changeset/proud-humans-work.md b/.changeset/proud-humans-work.md deleted file mode 100644 index 695ef29e6..000000000 --- a/.changeset/proud-humans-work.md +++ /dev/null @@ -1,10 +0,0 @@ ---- -'@cloud-carbon-footprint/app': patch -'@cloud-carbon-footprint/aws': patch -'@cloud-carbon-footprint/cli': patch -'@cloud-carbon-footprint/client': patch -'@cloud-carbon-footprint/core': patch -'@cloud-carbon-footprint/create-app': patch ---- - -bumps ramda dependency diff --git a/.changeset/shaggy-melons-exercise.md b/.changeset/shaggy-melons-exercise.md deleted file mode 100644 index 2a4000695..000000000 --- a/.changeset/shaggy-melons-exercise.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@cloud-carbon-footprint/azure': minor ---- - -updates azure recs to support skuchange diff --git a/packages/api/CHANGELOG.md b/packages/api/CHANGELOG.md index 4b9449a15..d6b972604 100644 --- a/packages/api/CHANGELOG.md +++ b/packages/api/CHANGELOG.md @@ -1,5 +1,13 @@ # @cloud-carbon-footprint/api +## 1.6.1 + +### Patch Changes + +- bcb77429: fixes mock data and integration tests +- Updated dependencies [65386330] + - @cloud-carbon-footprint/app@1.2.1 + ## 1.6.0 ### Minor Changes diff --git a/packages/api/package.json b/packages/api/package.json index 5efbeaba1..9333bfead 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -1,6 +1,6 @@ { "name": "@cloud-carbon-footprint/api", - "version": "1.6.0", + "version": "1.6.1", "license": "Apache-2.0", "description": "The API endpoint as an entrypoint to get cloud energy and carbon emissions. Optionally used by the client dashboard.", "main": "src/server.ts", @@ -71,7 +71,7 @@ "typescript": "^4.6.2" }, "dependencies": { - "@cloud-carbon-footprint/app": "^1.1.0", + "@cloud-carbon-footprint/app": "^1.2.1", "@cloud-carbon-footprint/common": "^1.9.0", "@types/express": "^4.17.12", "cors": "^2.8.5", diff --git a/packages/app/CHANGELOG.md b/packages/app/CHANGELOG.md index 493501725..0bbb8f878 100644 --- a/packages/app/CHANGELOG.md +++ b/packages/app/CHANGELOG.md @@ -1,5 +1,21 @@ # @cloud-carbon-footprint/app +## 1.2.1 + +### Patch Changes + +- 65386330: bumps ramda dependency +- Updated dependencies [e0299c2c] +- Updated dependencies [2510e2a7] +- Updated dependencies [efbcad00] +- Updated dependencies [5b5a0ac5] +- Updated dependencies [56273914] +- Updated dependencies [65386330] +- Updated dependencies [b98fd6d1] + - @cloud-carbon-footprint/gcp@0.12.0 + - @cloud-carbon-footprint/aws@0.14.4 + - @cloud-carbon-footprint/azure@1.3.0 + ## 1.2.0 ### Minor Changes diff --git a/packages/app/package.json b/packages/app/package.json index 934cca6b0..aa4fcd2c6 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -1,6 +1,6 @@ { "name": "@cloud-carbon-footprint/app", - "version": "1.2.0", + "version": "1.2.1", "license": "Apache-2.0", "description": "The logic to bootstrap the cloud-carbon-footprint server-side application", "main": "src/index.ts", @@ -40,10 +40,10 @@ "lint:fix": "eslint '*/**/*.ts' --quiet --fix" }, "dependencies": { - "@cloud-carbon-footprint/aws": "^0.14.3", - "@cloud-carbon-footprint/azure": "^1.2.0", + "@cloud-carbon-footprint/aws": "^0.14.4", + "@cloud-carbon-footprint/azure": "^1.3.0", "@cloud-carbon-footprint/common": "^1.10.0", - "@cloud-carbon-footprint/gcp": "^0.11.0", + "@cloud-carbon-footprint/gcp": "^0.12.0", "@cloud-carbon-footprint/on-premise": "^0.1.1", "@google-cloud/monitoring": "^2.3.5", "@google-cloud/storage": "^5.16.1", diff --git a/packages/aws/CHANGELOG.md b/packages/aws/CHANGELOG.md index 8739e7b9c..e33cc06f8 100644 --- a/packages/aws/CHANGELOG.md +++ b/packages/aws/CHANGELOG.md @@ -1,5 +1,14 @@ # @cloud-carbon-footprint/aws +## 0.14.4 + +### Patch Changes + +- 2510e2a7: Adds support for additional usage types +- 65386330: bumps ramda dependency +- Updated dependencies [65386330] + - @cloud-carbon-footprint/core@0.17.3 + ## 0.14.3 ### Patch Changes diff --git a/packages/aws/package.json b/packages/aws/package.json index 22962b560..fea9c2816 100644 --- a/packages/aws/package.json +++ b/packages/aws/package.json @@ -1,6 +1,6 @@ { "name": "@cloud-carbon-footprint/aws", - "version": "0.14.3", + "version": "0.14.4", "license": "Apache-2.0", "description": "The core logic to get cloud usage data and estimate energy and carbon emissions from Amazon Web Services.", "main": "src/index.ts", @@ -42,7 +42,7 @@ }, "dependencies": { "@cloud-carbon-footprint/common": "^1.10.0", - "@cloud-carbon-footprint/core": "^0.17.1", + "@cloud-carbon-footprint/core": "^0.17.3", "@google-cloud/iam-credentials": "^1.1.1", "aws-sdk": "^2.927.0", "aws-sdk-mock": "^5.1.0", diff --git a/packages/azure/CHANGELOG.md b/packages/azure/CHANGELOG.md index bc4e4c46d..255627bc6 100644 --- a/packages/azure/CHANGELOG.md +++ b/packages/azure/CHANGELOG.md @@ -1,5 +1,17 @@ # @cloud-carbon-footprint/azure +## 1.3.0 + +### Minor Changes + +- 5b5a0ac5: Updates support to version 9.2 of the @azure/arm-consumption package +- b98fd6d1: updates azure recs to support skuchange + +### Patch Changes + +- Updated dependencies [65386330] + - @cloud-carbon-footprint/core@0.17.3 + ## 1.2.0 ### Minor Changes diff --git a/packages/azure/package.json b/packages/azure/package.json index 294826bc5..a0f2efeef 100644 --- a/packages/azure/package.json +++ b/packages/azure/package.json @@ -1,6 +1,6 @@ { "name": "@cloud-carbon-footprint/azure", - "version": "1.2.0", + "version": "1.3.0", "license": "Apache-2.0", "description": "The core logic to get cloud usage data and estimate energy and carbon emissions from Microsoft Azure.", "main": "src/index.ts", @@ -48,7 +48,7 @@ "@azure/ms-rest-js": "^2.6.6", "@azure/ms-rest-nodeauth": "^3.1.1", "@cloud-carbon-footprint/common": "^1.10.0", - "@cloud-carbon-footprint/core": "^0.17.1", + "@cloud-carbon-footprint/core": "^0.17.3", "@google-cloud/secret-manager": "^3.10.1", "moment": "^2.29.1" }, diff --git a/packages/cli/CHANGELOG.md b/packages/cli/CHANGELOG.md index 71aaca666..905406d0b 100644 --- a/packages/cli/CHANGELOG.md +++ b/packages/cli/CHANGELOG.md @@ -1,5 +1,13 @@ # @cloud-carbon-footprint/cli +## 1.10.1 + +### Patch Changes + +- 65386330: bumps ramda dependency +- Updated dependencies [65386330] + - @cloud-carbon-footprint/app@1.2.1 + ## 1.10.0 ### Minor Changes diff --git a/packages/cli/package.json b/packages/cli/package.json index 0dcaab347..46f52875d 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@cloud-carbon-footprint/cli", - "version": "1.10.0", + "version": "1.10.1", "license": "Apache-2.0", "description": "Command Line Interface as an entrypoint to get cloud energy and carbon emissions.", "main": "src/index.ts", @@ -70,7 +70,7 @@ "dependencies": { "@azure/arm-consumption": "^9.2.0", "@azure/arm-resources-subscriptions": "^2.0.2", - "@cloud-carbon-footprint/app": "1.2.0", + "@cloud-carbon-footprint/app": "1.2.1", "@cloud-carbon-footprint/common": "^1.10.0", "@types/cli-table": "^0.3.0", "@types/prompts": "^2.0.12", diff --git a/packages/client/CHANGELOG.md b/packages/client/CHANGELOG.md index ba12ca768..4380c6c65 100644 --- a/packages/client/CHANGELOG.md +++ b/packages/client/CHANGELOG.md @@ -1,5 +1,12 @@ # @cloud-carbon-footprint/client +## 4.1.1 + +### Patch Changes + +- bcb77429: fixes mock data and integration tests +- 65386330: bumps ramda dependency + ## 4.1.0 ### Minor Changes diff --git a/packages/client/package.json b/packages/client/package.json index e51be2bb8..c44cde9ed 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -1,6 +1,6 @@ { "name": "@cloud-carbon-footprint/client", - "version": "4.1.0", + "version": "4.1.1", "license": "Apache-2.0", "description": "The front-end dashboard for Cloud Carbon Footprint.", "main": "src/index.tsx", diff --git a/packages/core/CHANGELOG.md b/packages/core/CHANGELOG.md index 143f82d0f..4fc928e60 100644 --- a/packages/core/CHANGELOG.md +++ b/packages/core/CHANGELOG.md @@ -1,5 +1,11 @@ # @cloud-carbon-footprint/core +## 0.17.3 + +### Patch Changes + +- 65386330: bumps ramda dependency + ## 0.17.2 ### Patch Changes diff --git a/packages/core/package.json b/packages/core/package.json index 105a714b5..81dbeb042 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@cloud-carbon-footprint/core", - "version": "0.17.2", + "version": "0.17.3", "license": "Apache-2.0", "description": "The core logic to get cloud usage data and estimate energy and carbon emissions.", "main": "src/index.ts", diff --git a/packages/create-app/CHANGELOG.md b/packages/create-app/CHANGELOG.md index 7676f4f59..51a674eb8 100644 --- a/packages/create-app/CHANGELOG.md +++ b/packages/create-app/CHANGELOG.md @@ -1,5 +1,13 @@ # @cloud-carbon-footprint/create-app +## 2.3.4 + +### Patch Changes + +- bcb77429: fixes mock data and integration tests +- 5e897485: Removes version locks for azure sdk +- 65386330: bumps ramda dependency + ## 2.3.3 ### Patch Changes diff --git a/packages/create-app/package.json b/packages/create-app/package.json index 051a67bf2..325816756 100644 --- a/packages/create-app/package.json +++ b/packages/create-app/package.json @@ -1,6 +1,6 @@ { "name": "@cloud-carbon-footprint/create-app", - "version": "2.3.3", + "version": "2.3.4", "license": "Apache-2.0", "description": "Create app package for Cloud Carbon Footprint", "main": "dist/index.js", diff --git a/packages/gcp/CHANGELOG.md b/packages/gcp/CHANGELOG.md index 72a53ce09..b7ceb5647 100644 --- a/packages/gcp/CHANGELOG.md +++ b/packages/gcp/CHANGELOG.md @@ -1,5 +1,18 @@ # @cloud-carbon-footprint/gcp +## 0.12.0 + +### Minor Changes + +- e0299c2c: Replaces the use of googleapis with separate package clients +- efbcad00: Adds support for including tags and labels in queries via config + +### Patch Changes + +- 56273914: adds DCU to gcp compute types +- Updated dependencies [65386330] + - @cloud-carbon-footprint/core@0.17.3 + ## 0.11.1 ### Patch Changes diff --git a/packages/gcp/package.json b/packages/gcp/package.json index d8a4b5e14..07b3581d0 100644 --- a/packages/gcp/package.json +++ b/packages/gcp/package.json @@ -1,6 +1,6 @@ { "name": "@cloud-carbon-footprint/gcp", - "version": "0.11.1", + "version": "0.12.0", "license": "Apache-2.0", "description": "The core logic to get cloud usage data and estimate energy and carbon emissions from Google Cloud Platform.", "main": "src/index.ts", @@ -42,7 +42,7 @@ }, "dependencies": { "@cloud-carbon-footprint/common": "^1.9.0", - "@cloud-carbon-footprint/core": "^0.17.0", + "@cloud-carbon-footprint/core": "^0.17.3", "@google-cloud/bigquery": "^5.9.3", "@google-cloud/compute": "^3.9.0", "@google-cloud/monitoring": "^2.3.5", diff --git a/packages/integration-tests/CHANGELOG.md b/packages/integration-tests/CHANGELOG.md index 1b6805358..07119469f 100644 --- a/packages/integration-tests/CHANGELOG.md +++ b/packages/integration-tests/CHANGELOG.md @@ -1,5 +1,11 @@ # @cloud-carbon-footprint/integration-tests +## 1.1.7 + +### Patch Changes + +- bcb77429: fixes mock data and integration tests + ## 1.1.6 ### Patch Changes diff --git a/packages/integration-tests/package.json b/packages/integration-tests/package.json index 4d96d24e1..8e132e045 100644 --- a/packages/integration-tests/package.json +++ b/packages/integration-tests/package.json @@ -1,6 +1,6 @@ { "name": "@cloud-carbon-footprint/integration-tests", - "version": "1.1.6", + "version": "1.1.7", "private": true, "description": "Test repository to run integration tests", "scripts": { diff --git a/yarn.lock b/yarn.lock index 63f802555..e4c4d7e53 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2208,7 +2208,7 @@ __metadata: version: 0.0.0-use.local resolution: "@cloud-carbon-footprint/api@workspace:packages/api" dependencies: - "@cloud-carbon-footprint/app": ^1.1.0 + "@cloud-carbon-footprint/app": ^1.2.1 "@cloud-carbon-footprint/common": ^1.9.0 "@types/cors": ^2.8.12 "@types/express": ^4.17.12 @@ -2242,14 +2242,14 @@ __metadata: languageName: unknown linkType: soft -"@cloud-carbon-footprint/app@1.2.0, @cloud-carbon-footprint/app@^1.1.0, @cloud-carbon-footprint/app@workspace:packages/app": +"@cloud-carbon-footprint/app@1.2.1, @cloud-carbon-footprint/app@^1.2.1, @cloud-carbon-footprint/app@workspace:packages/app": version: 0.0.0-use.local resolution: "@cloud-carbon-footprint/app@workspace:packages/app" dependencies: - "@cloud-carbon-footprint/aws": ^0.14.3 - "@cloud-carbon-footprint/azure": ^1.2.0 + "@cloud-carbon-footprint/aws": ^0.14.4 + "@cloud-carbon-footprint/azure": ^1.3.0 "@cloud-carbon-footprint/common": ^1.10.0 - "@cloud-carbon-footprint/gcp": ^0.11.0 + "@cloud-carbon-footprint/gcp": ^0.12.0 "@cloud-carbon-footprint/on-premise": ^0.1.1 "@google-cloud/monitoring": ^2.3.5 "@google-cloud/storage": ^5.16.1 @@ -2276,12 +2276,12 @@ __metadata: languageName: unknown linkType: soft -"@cloud-carbon-footprint/aws@^0.14.3, @cloud-carbon-footprint/aws@workspace:packages/aws": +"@cloud-carbon-footprint/aws@^0.14.4, @cloud-carbon-footprint/aws@workspace:packages/aws": version: 0.0.0-use.local resolution: "@cloud-carbon-footprint/aws@workspace:packages/aws" dependencies: "@cloud-carbon-footprint/common": ^1.10.0 - "@cloud-carbon-footprint/core": ^0.17.1 + "@cloud-carbon-footprint/core": ^0.17.3 "@google-cloud/iam-credentials": ^1.1.1 "@types/jest": ^27.4.0 "@types/jest-when": ^3.5.0 @@ -2312,7 +2312,7 @@ __metadata: languageName: unknown linkType: soft -"@cloud-carbon-footprint/azure@^1.2.0, @cloud-carbon-footprint/azure@workspace:packages/azure": +"@cloud-carbon-footprint/azure@^1.3.0, @cloud-carbon-footprint/azure@workspace:packages/azure": version: 0.0.0-use.local resolution: "@cloud-carbon-footprint/azure@workspace:packages/azure" dependencies: @@ -2323,7 +2323,7 @@ __metadata: "@azure/ms-rest-js": ^2.6.6 "@azure/ms-rest-nodeauth": ^3.1.1 "@cloud-carbon-footprint/common": ^1.10.0 - "@cloud-carbon-footprint/core": ^0.17.1 + "@cloud-carbon-footprint/core": ^0.17.3 "@google-cloud/secret-manager": ^3.10.1 "@types/jest": ^27.4.0 "@types/jest-when": ^3.5.0 @@ -2356,7 +2356,7 @@ __metadata: dependencies: "@azure/arm-consumption": ^9.2.0 "@azure/arm-resources-subscriptions": ^2.0.2 - "@cloud-carbon-footprint/app": 1.2.0 + "@cloud-carbon-footprint/app": 1.2.1 "@cloud-carbon-footprint/common": ^1.10.0 "@types/cli-table": ^0.3.0 "@types/jest": ^27.4.0 @@ -2486,7 +2486,7 @@ __metadata: languageName: unknown linkType: soft -"@cloud-carbon-footprint/core@^0.17.0, @cloud-carbon-footprint/core@^0.17.1, @cloud-carbon-footprint/core@workspace:packages/core": +"@cloud-carbon-footprint/core@^0.17.0, @cloud-carbon-footprint/core@^0.17.3, @cloud-carbon-footprint/core@workspace:packages/core": version: 0.0.0-use.local resolution: "@cloud-carbon-footprint/core@workspace:packages/core" dependencies: @@ -2549,12 +2549,12 @@ __metadata: languageName: unknown linkType: soft -"@cloud-carbon-footprint/gcp@^0.11.0, @cloud-carbon-footprint/gcp@workspace:packages/gcp": +"@cloud-carbon-footprint/gcp@^0.12.0, @cloud-carbon-footprint/gcp@workspace:packages/gcp": version: 0.0.0-use.local resolution: "@cloud-carbon-footprint/gcp@workspace:packages/gcp" dependencies: "@cloud-carbon-footprint/common": ^1.9.0 - "@cloud-carbon-footprint/core": ^0.17.0 + "@cloud-carbon-footprint/core": ^0.17.3 "@google-cloud/bigquery": ^5.9.3 "@google-cloud/compute": ^3.9.0 "@google-cloud/monitoring": ^2.3.5 From b070bb21835b156fc8dfa968a30a6cf2fae6956d Mon Sep 17 00:00:00 2001 From: Cam Casher Date: Wed, 3 May 2023 13:35:10 -0600 Subject: [PATCH 059/251] fixes demo app workflow changes --- .github/workflows/demo-app.yml | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/.github/workflows/demo-app.yml b/.github/workflows/demo-app.yml index 9c70653df..28500e600 100644 --- a/.github/workflows/demo-app.yml +++ b/.github/workflows/demo-app.yml @@ -2,20 +2,15 @@ name: Deploy Demo App on: workflow_dispatch: - push: - branches: [trunk] - paths-ignore: - - 'microsite/**' - - '.github/workflows/deploy-microsite.yml' pull_request: branches: [trunk] types: [closed] paths-ignore: - 'microsite/**' - '.github/workflows/deploy-microsite.yml' - concurrency: - group: ${{ github.workflow }} - cancel-in-progress: true +concurrency: + group: ${{ github.workflow }} + cancel-in-progress: true jobs: deploy-demo-app: if: github.event.pull_request.head.ref == 'changeset-release/trunk' From 95b6d14f9ebd29508b9414cfed200d6c1c461a3f Mon Sep 17 00:00:00 2001 From: Cam Casher Date: Wed, 3 May 2023 13:42:01 -0600 Subject: [PATCH 060/251] temp update to demo workflow --- .github/workflows/demo-app.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/demo-app.yml b/.github/workflows/demo-app.yml index 28500e600..3afdf2dfe 100644 --- a/.github/workflows/demo-app.yml +++ b/.github/workflows/demo-app.yml @@ -13,7 +13,7 @@ concurrency: cancel-in-progress: true jobs: deploy-demo-app: - if: github.event.pull_request.head.ref == 'changeset-release/trunk' +# if: github.event.pull_request.head.ref == 'changeset-release/trunk' runs-on: ubuntu-latest container: image: node:16.19-alpine3.17 From a06a87d796fe5022d82a08a81fe38ae949b954fe Mon Sep 17 00:00:00 2001 From: Cam Casher Date: Wed, 3 May 2023 13:48:33 -0600 Subject: [PATCH 061/251] updates mock estimates --- packages/api/mock-estimates.json | 30 +- packages/client/stub-server/mockData.json | 1390 +---------------- .../packages/client/stub-server/mockData.json | 30 +- 3 files changed, 31 insertions(+), 1419 deletions(-) diff --git a/packages/api/mock-estimates.json b/packages/api/mock-estimates.json index aed37b74c..3af9294ea 100644 --- a/packages/api/mock-estimates.json +++ b/packages/api/mock-estimates.json @@ -1,31 +1,31 @@ [ -{"timestamp":"2023-04-01T06:00:00.000Z","serviceEstimates":[{"cloudProvider":"AWS","accountName":"aws account 0","serviceName":"ebs","kilowattHours":0.9099318685999922,"co2e":0.00034492696349833045,"cost":2.0611247334106126,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 0","serviceName":"s3","kilowattHours":0.000986946262424086,"co2e":4.0524803092142913e-7,"cost":2.341122015683861,"region":"us-east-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"ec2","kilowattHours":50.176579830111365,"co2e":0.016165238194127487,"cost":2.4154850519158613,"region":"us-west-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"rds","kilowattHours":50.15554852182787,"co2e":0.01615846260063172,"cost":1.8825350541114,"region":"us-west-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 0","serviceName":"lambda","kilowattHours":72.26453941514701,"co2e":0.027393246691560364,"cost":2.0084835420769656,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"GCP","accountName":"gcp account 4","serviceName":"computeEngine","kilowattHours":68.54797801901817,"co2e":0.03290302944912873,"cost":1.8688785461442674,"region":"us-east1","usesAverageCPUConstant":false},{"cloudProvider":"AZURE","accountName":"azure account 4","serviceName":"virtualMachines","kilowattHours":50.02721869884774,"co2e":0.011256124207240741,"cost":2.2910467204304323,"region":"UK South","usesAverageCPUConstant":false}]} +{"timestamp":"2023-05-01T06:00:00.000Z","serviceEstimates":[{"cloudProvider":"AWS","accountName":"aws account 0","serviceName":"ebs","kilowattHours":0.9099318685999922,"co2e":0.00034492696349833045,"cost":2.0611247334106126,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 0","serviceName":"s3","kilowattHours":0.000986946262424086,"co2e":4.0524803092142913e-7,"cost":2.341122015683861,"region":"us-east-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"ec2","kilowattHours":50.176579830111365,"co2e":0.016165238194127487,"cost":2.4154850519158613,"region":"us-west-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"rds","kilowattHours":50.15554852182787,"co2e":0.01615846260063172,"cost":1.8825350541114,"region":"us-west-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 0","serviceName":"lambda","kilowattHours":72.26453941514701,"co2e":0.027393246691560364,"cost":2.0084835420769656,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"GCP","accountName":"gcp account 4","serviceName":"computeEngine","kilowattHours":68.54797801901817,"co2e":0.03290302944912873,"cost":1.8688785461442674,"region":"us-east1","usesAverageCPUConstant":false},{"cloudProvider":"AZURE","accountName":"azure account 4","serviceName":"virtualMachines","kilowattHours":50.02721869884774,"co2e":0.011256124207240741,"cost":2.2910467204304323,"region":"UK South","usesAverageCPUConstant":false}]} , -{"timestamp":"2023-03-01T07:00:00.000Z","serviceEstimates":[{"cloudProvider":"AWS","accountName":"aws account 0","serviceName":"ebs","kilowattHours":0.9281382786569483,"co2e":0.00035182844915221075,"cost":2.225771432143803,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"s3","kilowattHours":0.00003270772530525257,"co2e":1.3430053672139147e-8,"cost":1.5028221506352941,"region":"us-east-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"ec2","kilowattHours":50.558103056454314,"co2e":0.016288152387388715,"cost":2.440745140900739,"region":"us-west-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"rds","kilowattHours":62.615457571422965,"co2e":0.02017263411941262,"cost":1.5955486508938537,"region":"us-west-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 0","serviceName":"lambda","kilowattHours":50.903421738830794,"co2e":0.01929590917511685,"cost":2.238206567880293,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"GCP","accountName":"gcp account 1","serviceName":"computeEngine","kilowattHours":65.28200479098749,"co2e":0.0050919963736970235,"cost":1.6177172869839958,"region":"us-west1","usesAverageCPUConstant":false},{"cloudProvider":"AZURE","accountName":"azure account 1","serviceName":"virtualMachines","kilowattHours":61.90994721222564,"co2e":0.013929738122750768,"cost":2.2081554041299234,"region":"UK South","usesAverageCPUConstant":false}]} +{"timestamp":"2023-04-01T06:00:00.000Z","serviceEstimates":[{"cloudProvider":"AWS","accountName":"aws account 0","serviceName":"ebs","kilowattHours":0.9281382786569483,"co2e":0.00035182844915221075,"cost":2.225771432143803,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"s3","kilowattHours":0.00003270772530525257,"co2e":1.3430053672139147e-8,"cost":1.5028221506352941,"region":"us-east-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"ec2","kilowattHours":50.558103056454314,"co2e":0.016288152387388715,"cost":2.440745140900739,"region":"us-west-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"rds","kilowattHours":62.615457571422965,"co2e":0.02017263411941262,"cost":1.5955486508938537,"region":"us-west-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 0","serviceName":"lambda","kilowattHours":50.903421738830794,"co2e":0.01929590917511685,"cost":2.238206567880293,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"GCP","accountName":"gcp account 1","serviceName":"computeEngine","kilowattHours":65.28200479098749,"co2e":0.0050919963736970235,"cost":1.6177172869839958,"region":"us-west1","usesAverageCPUConstant":false},{"cloudProvider":"AZURE","accountName":"azure account 1","serviceName":"virtualMachines","kilowattHours":61.90994721222564,"co2e":0.013929738122750768,"cost":2.2081554041299234,"region":"UK South","usesAverageCPUConstant":false}]} , -{"timestamp":"2023-02-01T07:00:00.000Z","serviceEstimates":[{"cloudProvider":"AWS","accountName":"aws account 1","serviceName":"ebs","kilowattHours":0.9290466782034781,"co2e":0.0003521727952599142,"cost":1.9170203054445436,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 1","serviceName":"s3","kilowattHours":0.00007414638904524828,"co2e":3.0445100513091304e-8,"cost":2.3496108515083787,"region":"us-east-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"ec2","kilowattHours":50.74719344234976,"co2e":0.016349071069741494,"cost":1.8854738557659787,"region":"us-west-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 4","serviceName":"rds","kilowattHours":62.70792733734538,"co2e":0.02020242482649055,"cost":1.9619661070136278,"region":"us-west-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 2","serviceName":"lambda","kilowattHours":50.18018466997432,"co2e":0.019021752422662495,"cost":1.7366812626585235,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"GCP","accountName":"gcp account 3","serviceName":"computeEngine","kilowattHours":50.642678514609926,"co2e":0.024308485687012764,"cost":2.112304415112801,"region":"us-east1","usesAverageCPUConstant":false},{"cloudProvider":"AZURE","accountName":"azure account 1","serviceName":"virtualMachines","kilowattHours":50.39790599515025,"co2e":0.011339528848908806,"cost":1.844015443986478,"region":"UK South","usesAverageCPUConstant":false}]} +{"timestamp":"2023-03-01T07:00:00.000Z","serviceEstimates":[{"cloudProvider":"AWS","accountName":"aws account 1","serviceName":"ebs","kilowattHours":0.9290466782034781,"co2e":0.0003521727952599142,"cost":1.9170203054445436,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 1","serviceName":"s3","kilowattHours":0.00007414638904524828,"co2e":3.0445100513091304e-8,"cost":2.3496108515083787,"region":"us-east-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"ec2","kilowattHours":50.74719344234976,"co2e":0.016349071069741494,"cost":1.8854738557659787,"region":"us-west-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 4","serviceName":"rds","kilowattHours":62.70792733734538,"co2e":0.02020242482649055,"cost":1.9619661070136278,"region":"us-west-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 2","serviceName":"lambda","kilowattHours":50.18018466997432,"co2e":0.019021752422662495,"cost":1.7366812626585235,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"GCP","accountName":"gcp account 3","serviceName":"computeEngine","kilowattHours":50.642678514609926,"co2e":0.024308485687012764,"cost":2.112304415112801,"region":"us-east1","usesAverageCPUConstant":false},{"cloudProvider":"AZURE","accountName":"azure account 1","serviceName":"virtualMachines","kilowattHours":50.39790599515025,"co2e":0.011339528848908806,"cost":1.844015443986478,"region":"UK South","usesAverageCPUConstant":false}]} , -{"timestamp":"2023-01-01T07:00:00.000Z","serviceEstimates":[{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"ebs","kilowattHours":0.7903104123827822,"co2e":0.0002995821777115289,"cost":1.5941517912292598,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"s3","kilowattHours":0.0008485340026475967,"co2e":3.484148497591244e-7,"cost":2.422188298668747,"region":"us-east-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 2","serviceName":"ec2","kilowattHours":50.571988266018494,"co2e":0.01629262574369838,"cost":1.813087747561918,"region":"us-west-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"rds","kilowattHours":50.54507810450079,"co2e":0.016283956177692707,"cost":2.3595674179586856,"region":"us-west-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 4","serviceName":"lambda","kilowattHours":50.31390279587026,"co2e":0.019072440818927745,"cost":1.6209696239449094,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"GCP","accountName":"gcp account 2","serviceName":"computeEngine","kilowattHours":50.995343212272175,"co2e":0.01290182183270486,"cost":2.291652099168084,"region":"us-west2","usesAverageCPUConstant":false},{"cloudProvider":"AZURE","accountName":"azure account 1","serviceName":"virtualMachines","kilowattHours":64.71563943290937,"co2e":0.014561018872404607,"cost":2.1519820863003964,"region":"UK South","usesAverageCPUConstant":false}]} +{"timestamp":"2023-02-01T07:00:00.000Z","serviceEstimates":[{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"ebs","kilowattHours":0.7903104123827822,"co2e":0.0002995821777115289,"cost":1.5941517912292598,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"s3","kilowattHours":0.0008485340026475967,"co2e":3.484148497591244e-7,"cost":2.422188298668747,"region":"us-east-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 2","serviceName":"ec2","kilowattHours":50.571988266018494,"co2e":0.01629262574369838,"cost":1.813087747561918,"region":"us-west-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"rds","kilowattHours":50.54507810450079,"co2e":0.016283956177692707,"cost":2.3595674179586856,"region":"us-west-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 4","serviceName":"lambda","kilowattHours":50.31390279587026,"co2e":0.019072440818927745,"cost":1.6209696239449094,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"GCP","accountName":"gcp account 2","serviceName":"computeEngine","kilowattHours":50.995343212272175,"co2e":0.01290182183270486,"cost":2.291652099168084,"region":"us-west2","usesAverageCPUConstant":false},{"cloudProvider":"AZURE","accountName":"azure account 1","serviceName":"virtualMachines","kilowattHours":64.71563943290937,"co2e":0.014561018872404607,"cost":2.1519820863003964,"region":"UK South","usesAverageCPUConstant":false}]} , -{"timestamp":"2022-12-01T07:00:00.000Z","serviceEstimates":[{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"ebs","kilowattHours":0.616242116061845,"co2e":0.00023359828269344752,"cost":1.7611637388166252,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 2","serviceName":"s3","kilowattHours":0.00009916949627876193,"co2e":4.071978852802988e-8,"cost":2.4426180887131026,"region":"us-east-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 2","serviceName":"ec2","kilowattHours":50.149667680040125,"co2e":0.016156567987475487,"cost":2.2645178139778706,"region":"us-west-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 4","serviceName":"rds","kilowattHours":50.15605062459718,"co2e":0.0161586243615746,"cost":2.345203244211236,"region":"us-west-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 2","serviceName":"lambda","kilowattHours":55.97282211541847,"co2e":0.021217561706469563,"cost":1.6084626580032653,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"GCP","accountName":"gcp account 0","serviceName":"computeEngine","kilowattHours":50.58370656686491,"co2e":0.02428017915209516,"cost":2.002632458514051,"region":"us-east1","usesAverageCPUConstant":false},{"cloudProvider":"AZURE","accountName":"azure account 2","serviceName":"virtualMachines","kilowattHours":50.91095442793027,"co2e":0.01145496474628431,"cost":1.9447149842967881,"region":"UK South","usesAverageCPUConstant":false}]} +{"timestamp":"2023-01-01T07:00:00.000Z","serviceEstimates":[{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"ebs","kilowattHours":0.616242116061845,"co2e":0.00023359828269344752,"cost":1.7611637388166252,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 2","serviceName":"s3","kilowattHours":0.00009916949627876193,"co2e":4.071978852802988e-8,"cost":2.4426180887131026,"region":"us-east-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 2","serviceName":"ec2","kilowattHours":50.149667680040125,"co2e":0.016156567987475487,"cost":2.2645178139778706,"region":"us-west-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 4","serviceName":"rds","kilowattHours":50.15605062459718,"co2e":0.0161586243615746,"cost":2.345203244211236,"region":"us-west-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 2","serviceName":"lambda","kilowattHours":55.97282211541847,"co2e":0.021217561706469563,"cost":1.6084626580032653,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"GCP","accountName":"gcp account 0","serviceName":"computeEngine","kilowattHours":50.58370656686491,"co2e":0.02428017915209516,"cost":2.002632458514051,"region":"us-east1","usesAverageCPUConstant":false},{"cloudProvider":"AZURE","accountName":"azure account 2","serviceName":"virtualMachines","kilowattHours":50.91095442793027,"co2e":0.01145496474628431,"cost":1.9447149842967881,"region":"UK South","usesAverageCPUConstant":false}]} , -{"timestamp":"2022-11-01T06:00:00.000Z","serviceEstimates":[{"cloudProvider":"AWS","accountName":"aws account 2","serviceName":"ebs","kilowattHours":0.8445236465308483,"co2e":0.00032013273416680216,"cost":1.8371062169192403,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 2","serviceName":"s3","kilowattHours":0.0002648754924918617,"co2e":1.0875999622109836e-7,"cost":2.236483110571217,"region":"us-east-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"ec2","kilowattHours":50.37629287099671,"co2e":0.016229579145370397,"cost":2.449362850757937,"region":"us-west-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"rds","kilowattHours":50.52526081119707,"co2e":0.016277571699760927,"cost":2.302982452488826,"region":"us-west-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"lambda","kilowattHours":50.357072062609866,"co2e":0.01908880494970146,"cost":2.3300950152526614,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"GCP","accountName":"gcp account 0","serviceName":"computeEngine","kilowattHours":53.70289639150512,"co2e":0.02577739026792246,"cost":1.8981217535028845,"region":"us-east1","usesAverageCPUConstant":false},{"cloudProvider":"AZURE","accountName":"azure account 4","serviceName":"virtualMachines","kilowattHours":50.82389577797691,"co2e":0.011435376550044804,"cost":1.5108425566125716,"region":"UK South","usesAverageCPUConstant":false}]} +{"timestamp":"2022-12-01T07:00:00.000Z","serviceEstimates":[{"cloudProvider":"AWS","accountName":"aws account 2","serviceName":"ebs","kilowattHours":0.8445236465308483,"co2e":0.00032013273416680216,"cost":1.8371062169192403,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 2","serviceName":"s3","kilowattHours":0.0002648754924918617,"co2e":1.0875999622109836e-7,"cost":2.236483110571217,"region":"us-east-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"ec2","kilowattHours":50.37629287099671,"co2e":0.016229579145370397,"cost":2.449362850757937,"region":"us-west-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"rds","kilowattHours":50.52526081119707,"co2e":0.016277571699760927,"cost":2.302982452488826,"region":"us-west-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"lambda","kilowattHours":50.357072062609866,"co2e":0.01908880494970146,"cost":2.3300950152526614,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"GCP","accountName":"gcp account 0","serviceName":"computeEngine","kilowattHours":53.70289639150512,"co2e":0.02577739026792246,"cost":1.8981217535028845,"region":"us-east1","usesAverageCPUConstant":false},{"cloudProvider":"AZURE","accountName":"azure account 4","serviceName":"virtualMachines","kilowattHours":50.82389577797691,"co2e":0.011435376550044804,"cost":1.5108425566125716,"region":"UK South","usesAverageCPUConstant":false}]} , -{"timestamp":"2022-10-01T06:00:00.000Z","serviceEstimates":[{"cloudProvider":"AWS","accountName":"aws account 0","serviceName":"ebs","kilowattHours":0.33278898489743725,"co2e":0.00012614998771608664,"cost":2.201012388385645,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 1","serviceName":"s3","kilowattHours":0.00098701927251199,"co2e":4.052780094476032e-7,"cost":1.645103657701107,"region":"us-east-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 4","serviceName":"ec2","kilowattHours":50.904310297531644,"co2e":0.016399688935624875,"cost":2.235818514499144,"region":"us-west-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"rds","kilowattHours":50.12852654254913,"co2e":0.016149757010633425,"cost":1.5786907055484218,"region":"us-west-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 2","serviceName":"lambda","kilowattHours":59.54349975897656,"co2e":0.022571094910135484,"cost":2.324453671310117,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"GCP","accountName":"gcp account 3","serviceName":"computeEngine","kilowattHours":63.69530490695243,"co2e":0.03057374635533717,"cost":2.4104193295098923,"region":"us-east1","usesAverageCPUConstant":false},{"cloudProvider":"AZURE","accountName":"azure account 0","serviceName":"virtualMachines","kilowattHours":65.60925208439582,"co2e":0.014762081718989059,"cost":2.4070137425368685,"region":"UK South","usesAverageCPUConstant":false}]} +{"timestamp":"2022-11-01T06:00:00.000Z","serviceEstimates":[{"cloudProvider":"AWS","accountName":"aws account 0","serviceName":"ebs","kilowattHours":0.33278898489743725,"co2e":0.00012614998771608664,"cost":2.201012388385645,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 1","serviceName":"s3","kilowattHours":0.00098701927251199,"co2e":4.052780094476032e-7,"cost":1.645103657701107,"region":"us-east-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 4","serviceName":"ec2","kilowattHours":50.904310297531644,"co2e":0.016399688935624875,"cost":2.235818514499144,"region":"us-west-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"rds","kilowattHours":50.12852654254913,"co2e":0.016149757010633425,"cost":1.5786907055484218,"region":"us-west-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 2","serviceName":"lambda","kilowattHours":59.54349975897656,"co2e":0.022571094910135484,"cost":2.324453671310117,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"GCP","accountName":"gcp account 3","serviceName":"computeEngine","kilowattHours":63.69530490695243,"co2e":0.03057374635533717,"cost":2.4104193295098923,"region":"us-east1","usesAverageCPUConstant":false},{"cloudProvider":"AZURE","accountName":"azure account 0","serviceName":"virtualMachines","kilowattHours":65.60925208439582,"co2e":0.014762081718989059,"cost":2.4070137425368685,"region":"UK South","usesAverageCPUConstant":false}]} , -{"timestamp":"2022-09-01T06:00:00.000Z","serviceEstimates":[{"cloudProvider":"AWS","accountName":"aws account 4","serviceName":"ebs","kilowattHours":0.7421818551593697,"co2e":0.00028133813365340714,"cost":2.498136748374863,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 1","serviceName":"s3","kilowattHours":0.0002180651158962681,"co2e":8.953928110793486e-8,"cost":1.7632736153406632,"region":"us-east-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 0","serviceName":"ec2","kilowattHours":54.239336047864754,"co2e":0.017474124176532442,"cost":1.9661288302344184,"region":"us-west-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 0","serviceName":"rds","kilowattHours":50.23120235816861,"co2e":0.016182835770124106,"cost":2.0142111162898986,"region":"us-west-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"lambda","kilowattHours":50.86615400739505,"co2e":0.019281782133429234,"cost":2.164622206937599,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"GCP","accountName":"gcp account 3","serviceName":"computeEngine","kilowattHours":59.19683162717123,"co2e":0.02841447918104219,"cost":1.6906306697783389,"region":"us-east1","usesAverageCPUConstant":false},{"cloudProvider":"AZURE","accountName":"azure account 2","serviceName":"virtualMachines","kilowattHours":50.27713613081028,"co2e":0.011312355629432311,"cost":1.7863670364270168,"region":"UK South","usesAverageCPUConstant":false}]} +{"timestamp":"2022-10-01T06:00:00.000Z","serviceEstimates":[{"cloudProvider":"AWS","accountName":"aws account 4","serviceName":"ebs","kilowattHours":0.7421818551593697,"co2e":0.00028133813365340714,"cost":2.498136748374863,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 1","serviceName":"s3","kilowattHours":0.0002180651158962681,"co2e":8.953928110793486e-8,"cost":1.7632736153406632,"region":"us-east-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 0","serviceName":"ec2","kilowattHours":54.239336047864754,"co2e":0.017474124176532442,"cost":1.9661288302344184,"region":"us-west-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 0","serviceName":"rds","kilowattHours":50.23120235816861,"co2e":0.016182835770124106,"cost":2.0142111162898986,"region":"us-west-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"lambda","kilowattHours":50.86615400739505,"co2e":0.019281782133429234,"cost":2.164622206937599,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"GCP","accountName":"gcp account 3","serviceName":"computeEngine","kilowattHours":59.19683162717123,"co2e":0.02841447918104219,"cost":1.6906306697783389,"region":"us-east1","usesAverageCPUConstant":false},{"cloudProvider":"AZURE","accountName":"azure account 2","serviceName":"virtualMachines","kilowattHours":50.27713613081028,"co2e":0.011312355629432311,"cost":1.7863670364270168,"region":"UK South","usesAverageCPUConstant":false}]} , -{"timestamp":"2022-08-01T06:00:00.000Z","serviceEstimates":[{"cloudProvider":"AWS","accountName":"aws account 0","serviceName":"ebs","kilowattHours":0.5648293345133781,"co2e":0.00021410929100465172,"cost":2.3304265345916653,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 4","serviceName":"s3","kilowattHours":0.00005263625919629122,"co2e":2.161286911607075e-8,"cost":1.6038362843689178,"region":"us-east-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 0","serviceName":"ec2","kilowattHours":64.1169430041115,"co2e":0.02065636317680559,"cost":1.8957903016901754,"region":"us-west-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 1","serviceName":"rds","kilowattHours":50.69439982629539,"co2e":0.016332062708838108,"cost":2.3649041975938676,"region":"us-west-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 4","serviceName":"lambda","kilowattHours":50.69873776324742,"co2e":0.01921831982517644,"cost":2.1212012771669846,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"GCP","accountName":"gcp account 2","serviceName":"computeEngine","kilowattHours":50.71620115791394,"co2e":0.024343776555798693,"cost":2.0052165772682793,"region":"us-east1","usesAverageCPUConstant":false},{"cloudProvider":"AZURE","accountName":"azure account 0","serviceName":"virtualMachines","kilowattHours":50.146548832423804,"co2e":0.011282973487295355,"cost":2.4727258099898037,"region":"UK South","usesAverageCPUConstant":false}]} +{"timestamp":"2022-09-01T06:00:00.000Z","serviceEstimates":[{"cloudProvider":"AWS","accountName":"aws account 0","serviceName":"ebs","kilowattHours":0.5648293345133781,"co2e":0.00021410929100465172,"cost":2.3304265345916653,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 4","serviceName":"s3","kilowattHours":0.00005263625919629122,"co2e":2.161286911607075e-8,"cost":1.6038362843689178,"region":"us-east-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 0","serviceName":"ec2","kilowattHours":64.1169430041115,"co2e":0.02065636317680559,"cost":1.8957903016901754,"region":"us-west-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 1","serviceName":"rds","kilowattHours":50.69439982629539,"co2e":0.016332062708838108,"cost":2.3649041975938676,"region":"us-west-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 4","serviceName":"lambda","kilowattHours":50.69873776324742,"co2e":0.01921831982517644,"cost":2.1212012771669846,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"GCP","accountName":"gcp account 2","serviceName":"computeEngine","kilowattHours":50.71620115791394,"co2e":0.024343776555798693,"cost":2.0052165772682793,"region":"us-east1","usesAverageCPUConstant":false},{"cloudProvider":"AZURE","accountName":"azure account 0","serviceName":"virtualMachines","kilowattHours":50.146548832423804,"co2e":0.011282973487295355,"cost":2.4727258099898037,"region":"UK South","usesAverageCPUConstant":false}]} , -{"timestamp":"2022-07-01T06:00:00.000Z","serviceEstimates":[{"cloudProvider":"AWS","accountName":"aws account 0","serviceName":"ebs","kilowattHours":0.35269028620932774,"co2e":0.00013369395410308364,"cost":2.426536030208615,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 2","serviceName":"s3","kilowattHours":0.00007772936246220752,"co2e":3.1916298061882105e-8,"cost":1.9926068310477163,"region":"us-east-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 4","serviceName":"ec2","kilowattHours":50.00593572462313,"co2e":0.016110262294594658,"cost":1.9838511921282318,"region":"us-west-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"rds","kilowattHours":50.803461347266776,"co2e":0.016367198731864895,"cost":2.229003808663734,"region":"us-west-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 0","serviceName":"lambda","kilowattHours":52.62976125953012,"co2e":0.019950310970888823,"cost":1.6972395592555778,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"GCP","accountName":"gcp account 4","serviceName":"computeEngine","kilowattHours":64.63052755166217,"co2e":0.03102265322479784,"cost":2.30459191166866,"region":"us-east1","usesAverageCPUConstant":false},{"cloudProvider":"AZURE","accountName":"azure account 1","serviceName":"virtualMachines","kilowattHours":53.31425378242701,"co2e":0.011995707101046077,"cost":1.6930870858502807,"region":"UK South","usesAverageCPUConstant":false}]} +{"timestamp":"2022-08-01T06:00:00.000Z","serviceEstimates":[{"cloudProvider":"AWS","accountName":"aws account 0","serviceName":"ebs","kilowattHours":0.35269028620932774,"co2e":0.00013369395410308364,"cost":2.426536030208615,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 2","serviceName":"s3","kilowattHours":0.00007772936246220752,"co2e":3.1916298061882105e-8,"cost":1.9926068310477163,"region":"us-east-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 4","serviceName":"ec2","kilowattHours":50.00593572462313,"co2e":0.016110262294594658,"cost":1.9838511921282318,"region":"us-west-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"rds","kilowattHours":50.803461347266776,"co2e":0.016367198731864895,"cost":2.229003808663734,"region":"us-west-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 0","serviceName":"lambda","kilowattHours":52.62976125953012,"co2e":0.019950310970888823,"cost":1.6972395592555778,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"GCP","accountName":"gcp account 4","serviceName":"computeEngine","kilowattHours":64.63052755166217,"co2e":0.03102265322479784,"cost":2.30459191166866,"region":"us-east1","usesAverageCPUConstant":false},{"cloudProvider":"AZURE","accountName":"azure account 1","serviceName":"virtualMachines","kilowattHours":53.31425378242701,"co2e":0.011995707101046077,"cost":1.6930870858502807,"region":"UK South","usesAverageCPUConstant":false}]} , -{"timestamp":"2022-06-01T06:00:00.000Z","serviceEstimates":[{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"ebs","kilowattHours":0.3793961762889928,"co2e":0.00014381732914969223,"cost":1.5660349090236223,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 2","serviceName":"s3","kilowattHours":0.0007881261941471649,"co2e":3.2361092032637913e-7,"cost":2.107104300712024,"region":"us-east-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"ec2","kilowattHours":50.4099085739953,"co2e":0.016240409015558344,"cost":1.7912671435326524,"region":"us-west-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 1","serviceName":"rds","kilowattHours":56.61135475563019,"co2e":0.01823831032755711,"cost":2.0547082075494254,"region":"us-west-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 4","serviceName":"lambda","kilowattHours":50.16319007556745,"co2e":0.019015310298755278,"cost":2.3442440999125886,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"GCP","accountName":"gcp account 1","serviceName":"computeEngine","kilowattHours":50.2235650164992,"co2e":0.02410731120791962,"cost":1.6300863324887378,"region":"us-east1","usesAverageCPUConstant":false},{"cloudProvider":"AZURE","accountName":"azure account 3","serviceName":"virtualMachines","kilowattHours":50.27485053251475,"co2e":0.011311841369815818,"cost":1.7666414199697307,"region":"UK South","usesAverageCPUConstant":false}]} +{"timestamp":"2022-07-01T06:00:00.000Z","serviceEstimates":[{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"ebs","kilowattHours":0.3793961762889928,"co2e":0.00014381732914969223,"cost":1.5660349090236223,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 2","serviceName":"s3","kilowattHours":0.0007881261941471649,"co2e":3.2361092032637913e-7,"cost":2.107104300712024,"region":"us-east-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"ec2","kilowattHours":50.4099085739953,"co2e":0.016240409015558344,"cost":1.7912671435326524,"region":"us-west-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 1","serviceName":"rds","kilowattHours":56.61135475563019,"co2e":0.01823831032755711,"cost":2.0547082075494254,"region":"us-west-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 4","serviceName":"lambda","kilowattHours":50.16319007556745,"co2e":0.019015310298755278,"cost":2.3442440999125886,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"GCP","accountName":"gcp account 1","serviceName":"computeEngine","kilowattHours":50.2235650164992,"co2e":0.02410731120791962,"cost":1.6300863324887378,"region":"us-east1","usesAverageCPUConstant":false},{"cloudProvider":"AZURE","accountName":"azure account 3","serviceName":"virtualMachines","kilowattHours":50.27485053251475,"co2e":0.011311841369815818,"cost":1.7666414199697307,"region":"UK South","usesAverageCPUConstant":false}]} , -{"timestamp":"2022-05-01T06:00:00.000Z","serviceEstimates":[{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"ebs","kilowattHours":0.0025963667867079376,"co2e":9.842021614705911e-7,"cost":2.110850189120251,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"s3","kilowattHours":0.00038988295545011175,"co2e":1.600890605714595e-7,"cost":1.750590801038299,"region":"us-east-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 4","serviceName":"ec2","kilowattHours":50.58419719553395,"co2e":0.016296559057893588,"cost":2.331961666410219,"region":"us-west-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 1","serviceName":"rds","kilowattHours":50.09219672002729,"co2e":0.01613805274070103,"cost":2.171231385841728,"region":"us-west-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 0","serviceName":"lambda","kilowattHours":50.34788323768369,"co2e":0.01908532175102552,"cost":2.178860527900543,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"GCP","accountName":"gcp account 2","serviceName":"computeEngine","kilowattHours":50.876621782936894,"co2e":0.02442077845580971,"cost":2.008009566294815,"region":"us-east1","usesAverageCPUConstant":false},{"cloudProvider":"AZURE","accountName":"azure account 2","serviceName":"virtualMachines","kilowattHours":69.14280006058016,"co2e":0.015557130013630535,"cost":2.159049075087805,"region":"UK South","usesAverageCPUConstant":false}]} +{"timestamp":"2022-06-01T06:00:00.000Z","serviceEstimates":[{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"ebs","kilowattHours":0.0025963667867079376,"co2e":9.842021614705911e-7,"cost":2.110850189120251,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"s3","kilowattHours":0.00038988295545011175,"co2e":1.600890605714595e-7,"cost":1.750590801038299,"region":"us-east-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 4","serviceName":"ec2","kilowattHours":50.58419719553395,"co2e":0.016296559057893588,"cost":2.331961666410219,"region":"us-west-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 1","serviceName":"rds","kilowattHours":50.09219672002729,"co2e":0.01613805274070103,"cost":2.171231385841728,"region":"us-west-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 0","serviceName":"lambda","kilowattHours":50.34788323768369,"co2e":0.01908532175102552,"cost":2.178860527900543,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"GCP","accountName":"gcp account 2","serviceName":"computeEngine","kilowattHours":50.876621782936894,"co2e":0.02442077845580971,"cost":2.008009566294815,"region":"us-east1","usesAverageCPUConstant":false},{"cloudProvider":"AZURE","accountName":"azure account 2","serviceName":"virtualMachines","kilowattHours":69.14280006058016,"co2e":0.015557130013630535,"cost":2.159049075087805,"region":"UK South","usesAverageCPUConstant":false}]} , -{"timestamp":"2022-04-01T06:00:00.000Z","serviceEstimates":[{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"ebs","kilowattHours":0.847821319053617,"co2e":0.00032138277959233556,"cost":1.8756121359502171,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"s3","kilowattHours":0.000630093974723783,"co2e":2.587216267733831e-7,"cost":2.3698572941873914,"region":"us-east-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"ec2","kilowattHours":50.10307382046631,"co2e":0.016141556983518168,"cost":2.351022985532233,"region":"us-west-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 2","serviceName":"rds","kilowattHours":50.22293041559674,"co2e":0.016180170823201556,"cost":1.5327957463127884,"region":"us-west-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"lambda","kilowattHours":68.18586584927158,"co2e":0.025847147981617528,"cost":2.205957045144824,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"GCP","accountName":"gcp account 4","serviceName":"computeEngine","kilowattHours":68.47759811709409,"co2e":0.032869247096205166,"cost":2.026647361534485,"region":"us-east1","usesAverageCPUConstant":false},{"cloudProvider":"AZURE","accountName":"azure account 1","serviceName":"virtualMachines","kilowattHours":50.5125618456214,"co2e":0.011365326415264814,"cost":1.9833785822656285,"region":"UK South","usesAverageCPUConstant":false}]} +{"timestamp":"2022-05-01T06:00:00.000Z","serviceEstimates":[{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"ebs","kilowattHours":0.847821319053617,"co2e":0.00032138277959233556,"cost":1.8756121359502171,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"s3","kilowattHours":0.000630093974723783,"co2e":2.587216267733831e-7,"cost":2.3698572941873914,"region":"us-east-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"ec2","kilowattHours":50.10307382046631,"co2e":0.016141556983518168,"cost":2.351022985532233,"region":"us-west-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 2","serviceName":"rds","kilowattHours":50.22293041559674,"co2e":0.016180170823201556,"cost":1.5327957463127884,"region":"us-west-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"lambda","kilowattHours":68.18586584927158,"co2e":0.025847147981617528,"cost":2.205957045144824,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"GCP","accountName":"gcp account 4","serviceName":"computeEngine","kilowattHours":68.47759811709409,"co2e":0.032869247096205166,"cost":2.026647361534485,"region":"us-east1","usesAverageCPUConstant":false},{"cloudProvider":"AZURE","accountName":"azure account 1","serviceName":"virtualMachines","kilowattHours":50.5125618456214,"co2e":0.011365326415264814,"cost":1.9833785822656285,"region":"UK South","usesAverageCPUConstant":false}]} , -{"timestamp":"2022-03-01T07:00:00.000Z","serviceEstimates":[{"cloudProvider":"AWS","accountName":"aws account 1","serviceName":"ebs","kilowattHours":0.9119831149849784,"co2e":0.00034570452741424076,"cost":1.7466850895849164,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 1","serviceName":"s3","kilowattHours":0.00010168764085273696,"co2e":4.175375883526062e-8,"cost":2.0929755865584747,"region":"us-east-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 2","serviceName":"ec2","kilowattHours":50.13033413155082,"co2e":0.01615033935615933,"cost":2.030781896188273,"region":"us-west-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"rds","kilowattHours":53.81264913939785,"co2e":0.017336659735292387,"cost":1.7863504430322306,"region":"us-west-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 4","serviceName":"lambda","kilowattHours":63.45075892152083,"co2e":0.02405221573362198,"cost":1.8740007850011755,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"GCP","accountName":"gcp account 4","serviceName":"computeEngine","kilowattHours":50.77403726191187,"co2e":0.0243715378857177,"cost":1.7839515021969587,"region":"us-east1","usesAverageCPUConstant":false},{"cloudProvider":"AZURE","accountName":"azure account 4","serviceName":"virtualMachines","kilowattHours":50.373127573933836,"co2e":0.011333953704135112,"cost":1.8199290662411896,"region":"UK South","usesAverageCPUConstant":false}]} +{"timestamp":"2022-04-01T06:00:00.000Z","serviceEstimates":[{"cloudProvider":"AWS","accountName":"aws account 1","serviceName":"ebs","kilowattHours":0.9119831149849784,"co2e":0.00034570452741424076,"cost":1.7466850895849164,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 1","serviceName":"s3","kilowattHours":0.00010168764085273696,"co2e":4.175375883526062e-8,"cost":2.0929755865584747,"region":"us-east-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 2","serviceName":"ec2","kilowattHours":50.13033413155082,"co2e":0.01615033935615933,"cost":2.030781896188273,"region":"us-west-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"rds","kilowattHours":53.81264913939785,"co2e":0.017336659735292387,"cost":1.7863504430322306,"region":"us-west-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 4","serviceName":"lambda","kilowattHours":63.45075892152083,"co2e":0.02405221573362198,"cost":1.8740007850011755,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"GCP","accountName":"gcp account 4","serviceName":"computeEngine","kilowattHours":50.77403726191187,"co2e":0.0243715378857177,"cost":1.7839515021969587,"region":"us-east1","usesAverageCPUConstant":false},{"cloudProvider":"AZURE","accountName":"azure account 4","serviceName":"virtualMachines","kilowattHours":50.373127573933836,"co2e":0.011333953704135112,"cost":1.8199290662411896,"region":"UK South","usesAverageCPUConstant":false}]} , -{"timestamp":"2022-02-01T07:00:00.000Z","serviceEstimates":[{"cloudProvider":"AWS","accountName":"aws account 1","serviceName":"ebs","kilowattHours":0.8148394263939371,"co2e":0.00030888036652372336,"cost":1.9952062784974551,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 1","serviceName":"s3","kilowattHours":0.00010100550904187954,"co2e":4.147367005666808e-8,"cost":2.479801470978538,"region":"us-east-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 1","serviceName":"ec2","kilowattHours":50.933103987612505,"co2e":0.016408965312377156,"cost":2.014818576889102,"region":"us-west-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 2","serviceName":"rds","kilowattHours":50.92736661108625,"co2e":0.016407116918993825,"cost":2.0552869714092585,"region":"us-west-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 2","serviceName":"lambda","kilowattHours":70.47173348463286,"co2e":0.026713649540286294,"cost":1.8418849726705238,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"GCP","accountName":"gcp account 3","serviceName":"computeEngine","kilowattHours":57.666701915218034,"co2e":0.02768001691930466,"cost":1.611910606014586,"region":"us-east1","usesAverageCPUConstant":false},{"cloudProvider":"AZURE","accountName":"azure account 2","serviceName":"virtualMachines","kilowattHours":50.773157853289746,"co2e":0.011423960516990192,"cost":2.2728625664822433,"region":"UK South","usesAverageCPUConstant":false}]} +{"timestamp":"2022-03-01T07:00:00.000Z","serviceEstimates":[{"cloudProvider":"AWS","accountName":"aws account 1","serviceName":"ebs","kilowattHours":0.8148394263939371,"co2e":0.00030888036652372336,"cost":1.9952062784974551,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 1","serviceName":"s3","kilowattHours":0.00010100550904187954,"co2e":4.147367005666808e-8,"cost":2.479801470978538,"region":"us-east-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 1","serviceName":"ec2","kilowattHours":50.933103987612505,"co2e":0.016408965312377156,"cost":2.014818576889102,"region":"us-west-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 2","serviceName":"rds","kilowattHours":50.92736661108625,"co2e":0.016407116918993825,"cost":2.0552869714092585,"region":"us-west-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 2","serviceName":"lambda","kilowattHours":70.47173348463286,"co2e":0.026713649540286294,"cost":1.8418849726705238,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"GCP","accountName":"gcp account 3","serviceName":"computeEngine","kilowattHours":57.666701915218034,"co2e":0.02768001691930466,"cost":1.611910606014586,"region":"us-east1","usesAverageCPUConstant":false},{"cloudProvider":"AZURE","accountName":"azure account 2","serviceName":"virtualMachines","kilowattHours":50.773157853289746,"co2e":0.011423960516990192,"cost":2.2728625664822433,"region":"UK South","usesAverageCPUConstant":false}]} ] \ No newline at end of file diff --git a/packages/client/stub-server/mockData.json b/packages/client/stub-server/mockData.json index 1ceac5f79..b25b9639e 100644 --- a/packages/client/stub-server/mockData.json +++ b/packages/client/stub-server/mockData.json @@ -1,1389 +1 @@ -{ - "emissions": [ - { "region": "us-east-1", "mtPerKwHour": 0.000379069 }, - { "region": "us-east-2", "mtPerKwHour": 0.000410608 }, - { "region": "us-west-1", "mtPerKwHour": 0.000322167 }, - { "region": "us-west-2", "mtPerKwHour": 0.000322167 }, - { "region": "us-gov-east-1", "mtPerKwHour": 0.000379069 }, - { "region": "us-gov-west-1", "mtPerKwHour": 0.000322167 }, - { "region": "af-south-1", "mtPerKwHour": 0.0009006 }, - { "region": "ap-east-1", "mtPerKwHour": 0.00071 }, - { "region": "ap-south-1", "mtPerKwHour": 0.0007082 }, - { "region": "ap-northeast-3", "mtPerKwHour": 0.0004658 }, - { "region": "ap-northeast-2", "mtPerKwHour": 0.0004156 }, - { "region": "ap-southeast-1", "mtPerKwHour": 0.000408 }, - { "region": "ap-southeast-2", "mtPerKwHour": 0.00076 }, - { "region": "ap-northeast-1", "mtPerKwHour": 0.0004658 }, - { "region": "ca-central-1", "mtPerKwHour": 0.00012 }, - { "region": "cn-north-1", "mtPerKwHour": 0.0005374 }, - { "region": "cn-northwest-1", "mtPerKwHour": 0.0005374 }, - { "region": "eu-central-1", "mtPerKwHour": 0.000311 }, - { "region": "eu-west-1", "mtPerKwHour": 0.0002786 }, - { "region": "eu-west-2", "mtPerKwHour": 0.000225 }, - { "region": "eu-south-1", "mtPerKwHour": 0.0002134 }, - { "region": "eu-west-3", "mtPerKwHour": 0.0000511 }, - { "region": "eu-north-1", "mtPerKwHour": 0.0000088 }, - { "region": "me-south-1", "mtPerKwHour": 0.0005059 }, - { "region": "sa-east-1", "mtPerKwHour": 0.0000617 }, - { "region": "us-central1", "mtPerKwHour": 0.000454 }, - { "region": "us-central2", "mtPerKwHour": 0.000454 }, - { "region": "us-east1", "mtPerKwHour": 0.00048 }, - { "region": "us-east4", "mtPerKwHour": 0.000361 }, - { "region": "us-west1", "mtPerKwHour": 0.000078 }, - { "region": "us-west2", "mtPerKwHour": 0.000253 }, - { "region": "us-west3", "mtPerKwHour": 0.000533 }, - { "region": "us-west4", "mtPerKwHour": 0.000455 }, - { "region": "asia-east1", "mtPerKwHour": 0.00054 }, - { "region": "asia-east2", "mtPerKwHour": 0.000453 }, - { "region": "asia-northeast1", "mtPerKwHour": 0.000554 }, - { "region": "asia-northeast2", "mtPerKwHour": 0.000442 }, - { "region": "asia-northeast3", "mtPerKwHour": 0.000457 }, - { "region": "asia-south1", "mtPerKwHour": 0.000721 }, - { "region": "asia-southeast1", "mtPerKwHour": 0.000493 }, - { "region": "asia-southeast2", "mtPerKwHour": 0.000647 }, - { "region": "australia-southeast1", "mtPerKwHour": 0.000727 }, - { "region": "europe-north1", "mtPerKwHour": 0.000133 }, - { "region": "europe-west1", "mtPerKwHour": 0.000212 }, - { "region": "europe-west2", "mtPerKwHour": 0.000231 }, - { "region": "europe-west3", "mtPerKwHour": 0.000293 }, - { "region": "europe-west4", "mtPerKwHour": 0.00041 }, - { "region": "europe-west6", "mtPerKwHour": 0.000087 }, - { "region": "northamerica-northeast1", "mtPerKwHour": 0.000027 }, - { "region": "southamerica-east1", "mtPerKwHour": 0.000103 }, - { "region": "AP East", "mtPerKwHour": 0.00071 }, - { "region": "EU West", "mtPerKwHour": 0.0003284 }, - { "region": "IN Central", "mtPerKwHour": 0.0007082 }, - { "region": "UK South", "mtPerKwHour": 0.000225 }, - { "region": "UK West", "mtPerKwHour": 0.000225 }, - { "region": "US Central", "mtPerKwHour": 0.000426254 }, - { "region": "US East", "mtPerKwHour": 0.000379069 }, - { "region": "US South Central", "mtPerKwHour": 0.000373231 }, - { "region": "US West", "mtPerKwHour": 0.000322167 }, - { "region": "US West 2", "mtPerKwHour": 0.000322167 }, - { "region": "Unknown", "mtPerKwHour": 0.0003852304903666667 } - ], - "footprint": [ - { - "timestamp": "2023-04-01T06:00:00.000Z", - "serviceEstimates": [ - { - "cloudProvider": "AWS", - "accountName": "aws account 0", - "serviceName": "ebs", - "kilowattHours": 0.9099318685999922, - "co2e": 0.00034492696349833045, - "cost": 2.0611247334106126, - "region": "us-east-1", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AWS", - "accountName": "aws account 0", - "serviceName": "s3", - "kilowattHours": 0.000986946262424086, - "co2e": 4.0524803092142913e-7, - "cost": 2.341122015683861, - "region": "us-east-2", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AWS", - "accountName": "aws account 3", - "serviceName": "ec2", - "kilowattHours": 50.176579830111365, - "co2e": 0.016165238194127487, - "cost": 2.4154850519158613, - "region": "us-west-1", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AWS", - "accountName": "aws account 3", - "serviceName": "rds", - "kilowattHours": 50.15554852182787, - "co2e": 0.01615846260063172, - "cost": 1.8825350541114, - "region": "us-west-2", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AWS", - "accountName": "aws account 0", - "serviceName": "lambda", - "kilowattHours": 72.26453941514701, - "co2e": 0.027393246691560364, - "cost": 2.0084835420769656, - "region": "us-east-1", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "GCP", - "accountName": "gcp account 4", - "serviceName": "computeEngine", - "kilowattHours": 68.54797801901817, - "co2e": 0.03290302944912873, - "cost": 1.8688785461442674, - "region": "us-east1", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AZURE", - "accountName": "azure account 4", - "serviceName": "virtualMachines", - "kilowattHours": 50.02721869884774, - "co2e": 0.011256124207240741, - "cost": 2.2910467204304323, - "region": "UK South", - "usesAverageCPUConstant": false - } - ] - }, - { - "timestamp": "2023-03-01T07:00:00.000Z", - "serviceEstimates": [ - { - "cloudProvider": "AWS", - "accountName": "aws account 0", - "serviceName": "ebs", - "kilowattHours": 0.9281382786569483, - "co2e": 0.00035182844915221075, - "cost": 2.225771432143803, - "region": "us-east-1", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AWS", - "accountName": "aws account 3", - "serviceName": "s3", - "kilowattHours": 0.00003270772530525257, - "co2e": 1.3430053672139147e-8, - "cost": 1.5028221506352941, - "region": "us-east-2", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AWS", - "accountName": "aws account 3", - "serviceName": "ec2", - "kilowattHours": 50.558103056454314, - "co2e": 0.016288152387388715, - "cost": 2.440745140900739, - "region": "us-west-1", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AWS", - "accountName": "aws account 3", - "serviceName": "rds", - "kilowattHours": 62.615457571422965, - "co2e": 0.02017263411941262, - "cost": 1.5955486508938537, - "region": "us-west-2", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AWS", - "accountName": "aws account 0", - "serviceName": "lambda", - "kilowattHours": 50.903421738830794, - "co2e": 0.01929590917511685, - "cost": 2.238206567880293, - "region": "us-east-1", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "GCP", - "accountName": "gcp account 1", - "serviceName": "computeEngine", - "kilowattHours": 65.28200479098749, - "co2e": 0.0050919963736970235, - "cost": 1.6177172869839958, - "region": "us-west1", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AZURE", - "accountName": "azure account 1", - "serviceName": "virtualMachines", - "kilowattHours": 61.90994721222564, - "co2e": 0.013929738122750768, - "cost": 2.2081554041299234, - "region": "UK South", - "usesAverageCPUConstant": false - } - ] - }, - { - "timestamp": "2023-02-01T07:00:00.000Z", - "serviceEstimates": [ - { - "cloudProvider": "AWS", - "accountName": "aws account 1", - "serviceName": "ebs", - "kilowattHours": 0.9290466782034781, - "co2e": 0.0003521727952599142, - "cost": 1.9170203054445436, - "region": "us-east-1", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AWS", - "accountName": "aws account 1", - "serviceName": "s3", - "kilowattHours": 0.00007414638904524828, - "co2e": 3.0445100513091304e-8, - "cost": 2.3496108515083787, - "region": "us-east-2", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AWS", - "accountName": "aws account 3", - "serviceName": "ec2", - "kilowattHours": 50.74719344234976, - "co2e": 0.016349071069741494, - "cost": 1.8854738557659787, - "region": "us-west-1", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AWS", - "accountName": "aws account 4", - "serviceName": "rds", - "kilowattHours": 62.70792733734538, - "co2e": 0.02020242482649055, - "cost": 1.9619661070136278, - "region": "us-west-2", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AWS", - "accountName": "aws account 2", - "serviceName": "lambda", - "kilowattHours": 50.18018466997432, - "co2e": 0.019021752422662495, - "cost": 1.7366812626585235, - "region": "us-east-1", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "GCP", - "accountName": "gcp account 3", - "serviceName": "computeEngine", - "kilowattHours": 50.642678514609926, - "co2e": 0.024308485687012764, - "cost": 2.112304415112801, - "region": "us-east1", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AZURE", - "accountName": "azure account 1", - "serviceName": "virtualMachines", - "kilowattHours": 50.39790599515025, - "co2e": 0.011339528848908806, - "cost": 1.844015443986478, - "region": "UK South", - "usesAverageCPUConstant": false - } - ] - }, - { - "timestamp": "2023-01-01T07:00:00.000Z", - "serviceEstimates": [ - { - "cloudProvider": "AWS", - "accountName": "aws account 3", - "serviceName": "ebs", - "kilowattHours": 0.7903104123827822, - "co2e": 0.0002995821777115289, - "cost": 1.5941517912292598, - "region": "us-east-1", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AWS", - "accountName": "aws account 3", - "serviceName": "s3", - "kilowattHours": 0.0008485340026475967, - "co2e": 3.484148497591244e-7, - "cost": 2.422188298668747, - "region": "us-east-2", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AWS", - "accountName": "aws account 2", - "serviceName": "ec2", - "kilowattHours": 50.571988266018494, - "co2e": 0.01629262574369838, - "cost": 1.813087747561918, - "region": "us-west-1", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AWS", - "accountName": "aws account 3", - "serviceName": "rds", - "kilowattHours": 50.54507810450079, - "co2e": 0.016283956177692707, - "cost": 2.3595674179586856, - "region": "us-west-2", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AWS", - "accountName": "aws account 4", - "serviceName": "lambda", - "kilowattHours": 50.31390279587026, - "co2e": 0.019072440818927745, - "cost": 1.6209696239449094, - "region": "us-east-1", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "GCP", - "accountName": "gcp account 2", - "serviceName": "computeEngine", - "kilowattHours": 50.995343212272175, - "co2e": 0.01290182183270486, - "cost": 2.291652099168084, - "region": "us-west2", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AZURE", - "accountName": "azure account 1", - "serviceName": "virtualMachines", - "kilowattHours": 64.71563943290937, - "co2e": 0.014561018872404607, - "cost": 2.1519820863003964, - "region": "UK South", - "usesAverageCPUConstant": false - } - ] - }, - { - "timestamp": "2022-12-01T07:00:00.000Z", - "serviceEstimates": [ - { - "cloudProvider": "AWS", - "accountName": "aws account 3", - "serviceName": "ebs", - "kilowattHours": 0.616242116061845, - "co2e": 0.00023359828269344752, - "cost": 1.7611637388166252, - "region": "us-east-1", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AWS", - "accountName": "aws account 2", - "serviceName": "s3", - "kilowattHours": 0.00009916949627876193, - "co2e": 4.071978852802988e-8, - "cost": 2.4426180887131026, - "region": "us-east-2", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AWS", - "accountName": "aws account 2", - "serviceName": "ec2", - "kilowattHours": 50.149667680040125, - "co2e": 0.016156567987475487, - "cost": 2.2645178139778706, - "region": "us-west-1", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AWS", - "accountName": "aws account 4", - "serviceName": "rds", - "kilowattHours": 50.15605062459718, - "co2e": 0.0161586243615746, - "cost": 2.345203244211236, - "region": "us-west-2", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AWS", - "accountName": "aws account 2", - "serviceName": "lambda", - "kilowattHours": 55.97282211541847, - "co2e": 0.021217561706469563, - "cost": 1.6084626580032653, - "region": "us-east-1", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "GCP", - "accountName": "gcp account 0", - "serviceName": "computeEngine", - "kilowattHours": 50.58370656686491, - "co2e": 0.02428017915209516, - "cost": 2.002632458514051, - "region": "us-east1", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AZURE", - "accountName": "azure account 2", - "serviceName": "virtualMachines", - "kilowattHours": 50.91095442793027, - "co2e": 0.01145496474628431, - "cost": 1.9447149842967881, - "region": "UK South", - "usesAverageCPUConstant": false - } - ] - }, - { - "timestamp": "2022-11-01T06:00:00.000Z", - "serviceEstimates": [ - { - "cloudProvider": "AWS", - "accountName": "aws account 2", - "serviceName": "ebs", - "kilowattHours": 0.8445236465308483, - "co2e": 0.00032013273416680216, - "cost": 1.8371062169192403, - "region": "us-east-1", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AWS", - "accountName": "aws account 2", - "serviceName": "s3", - "kilowattHours": 0.0002648754924918617, - "co2e": 1.0875999622109836e-7, - "cost": 2.236483110571217, - "region": "us-east-2", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AWS", - "accountName": "aws account 3", - "serviceName": "ec2", - "kilowattHours": 50.37629287099671, - "co2e": 0.016229579145370397, - "cost": 2.449362850757937, - "region": "us-west-1", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AWS", - "accountName": "aws account 3", - "serviceName": "rds", - "kilowattHours": 50.52526081119707, - "co2e": 0.016277571699760927, - "cost": 2.302982452488826, - "region": "us-west-2", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AWS", - "accountName": "aws account 3", - "serviceName": "lambda", - "kilowattHours": 50.357072062609866, - "co2e": 0.01908880494970146, - "cost": 2.3300950152526614, - "region": "us-east-1", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "GCP", - "accountName": "gcp account 0", - "serviceName": "computeEngine", - "kilowattHours": 53.70289639150512, - "co2e": 0.02577739026792246, - "cost": 1.8981217535028845, - "region": "us-east1", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AZURE", - "accountName": "azure account 4", - "serviceName": "virtualMachines", - "kilowattHours": 50.82389577797691, - "co2e": 0.011435376550044804, - "cost": 1.5108425566125716, - "region": "UK South", - "usesAverageCPUConstant": false - } - ] - }, - { - "timestamp": "2022-10-01T06:00:00.000Z", - "serviceEstimates": [ - { - "cloudProvider": "AWS", - "accountName": "aws account 0", - "serviceName": "ebs", - "kilowattHours": 0.33278898489743725, - "co2e": 0.00012614998771608664, - "cost": 2.201012388385645, - "region": "us-east-1", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AWS", - "accountName": "aws account 1", - "serviceName": "s3", - "kilowattHours": 0.00098701927251199, - "co2e": 4.052780094476032e-7, - "cost": 1.645103657701107, - "region": "us-east-2", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AWS", - "accountName": "aws account 4", - "serviceName": "ec2", - "kilowattHours": 50.904310297531644, - "co2e": 0.016399688935624875, - "cost": 2.235818514499144, - "region": "us-west-1", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AWS", - "accountName": "aws account 3", - "serviceName": "rds", - "kilowattHours": 50.12852654254913, - "co2e": 0.016149757010633425, - "cost": 1.5786907055484218, - "region": "us-west-2", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AWS", - "accountName": "aws account 2", - "serviceName": "lambda", - "kilowattHours": 59.54349975897656, - "co2e": 0.022571094910135484, - "cost": 2.324453671310117, - "region": "us-east-1", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "GCP", - "accountName": "gcp account 3", - "serviceName": "computeEngine", - "kilowattHours": 63.69530490695243, - "co2e": 0.03057374635533717, - "cost": 2.4104193295098923, - "region": "us-east1", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AZURE", - "accountName": "azure account 0", - "serviceName": "virtualMachines", - "kilowattHours": 65.60925208439582, - "co2e": 0.014762081718989059, - "cost": 2.4070137425368685, - "region": "UK South", - "usesAverageCPUConstant": false - } - ] - }, - { - "timestamp": "2022-09-01T06:00:00.000Z", - "serviceEstimates": [ - { - "cloudProvider": "AWS", - "accountName": "aws account 4", - "serviceName": "ebs", - "kilowattHours": 0.7421818551593697, - "co2e": 0.00028133813365340714, - "cost": 2.498136748374863, - "region": "us-east-1", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AWS", - "accountName": "aws account 1", - "serviceName": "s3", - "kilowattHours": 0.0002180651158962681, - "co2e": 8.953928110793486e-8, - "cost": 1.7632736153406632, - "region": "us-east-2", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AWS", - "accountName": "aws account 0", - "serviceName": "ec2", - "kilowattHours": 54.239336047864754, - "co2e": 0.017474124176532442, - "cost": 1.9661288302344184, - "region": "us-west-1", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AWS", - "accountName": "aws account 0", - "serviceName": "rds", - "kilowattHours": 50.23120235816861, - "co2e": 0.016182835770124106, - "cost": 2.0142111162898986, - "region": "us-west-2", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AWS", - "accountName": "aws account 3", - "serviceName": "lambda", - "kilowattHours": 50.86615400739505, - "co2e": 0.019281782133429234, - "cost": 2.164622206937599, - "region": "us-east-1", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "GCP", - "accountName": "gcp account 3", - "serviceName": "computeEngine", - "kilowattHours": 59.19683162717123, - "co2e": 0.02841447918104219, - "cost": 1.6906306697783389, - "region": "us-east1", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AZURE", - "accountName": "azure account 2", - "serviceName": "virtualMachines", - "kilowattHours": 50.27713613081028, - "co2e": 0.011312355629432311, - "cost": 1.7863670364270168, - "region": "UK South", - "usesAverageCPUConstant": false - } - ] - }, - { - "timestamp": "2022-08-01T06:00:00.000Z", - "serviceEstimates": [ - { - "cloudProvider": "AWS", - "accountName": "aws account 0", - "serviceName": "ebs", - "kilowattHours": 0.5648293345133781, - "co2e": 0.00021410929100465172, - "cost": 2.3304265345916653, - "region": "us-east-1", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AWS", - "accountName": "aws account 4", - "serviceName": "s3", - "kilowattHours": 0.00005263625919629122, - "co2e": 2.161286911607075e-8, - "cost": 1.6038362843689178, - "region": "us-east-2", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AWS", - "accountName": "aws account 0", - "serviceName": "ec2", - "kilowattHours": 64.1169430041115, - "co2e": 0.02065636317680559, - "cost": 1.8957903016901754, - "region": "us-west-1", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AWS", - "accountName": "aws account 1", - "serviceName": "rds", - "kilowattHours": 50.69439982629539, - "co2e": 0.016332062708838108, - "cost": 2.3649041975938676, - "region": "us-west-2", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AWS", - "accountName": "aws account 4", - "serviceName": "lambda", - "kilowattHours": 50.69873776324742, - "co2e": 0.01921831982517644, - "cost": 2.1212012771669846, - "region": "us-east-1", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "GCP", - "accountName": "gcp account 2", - "serviceName": "computeEngine", - "kilowattHours": 50.71620115791394, - "co2e": 0.024343776555798693, - "cost": 2.0052165772682793, - "region": "us-east1", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AZURE", - "accountName": "azure account 0", - "serviceName": "virtualMachines", - "kilowattHours": 50.146548832423804, - "co2e": 0.011282973487295355, - "cost": 2.4727258099898037, - "region": "UK South", - "usesAverageCPUConstant": false - } - ] - }, - { - "timestamp": "2022-07-01T06:00:00.000Z", - "serviceEstimates": [ - { - "cloudProvider": "AWS", - "accountName": "aws account 0", - "serviceName": "ebs", - "kilowattHours": 0.35269028620932774, - "co2e": 0.00013369395410308364, - "cost": 2.426536030208615, - "region": "us-east-1", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AWS", - "accountName": "aws account 2", - "serviceName": "s3", - "kilowattHours": 0.00007772936246220752, - "co2e": 3.1916298061882105e-8, - "cost": 1.9926068310477163, - "region": "us-east-2", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AWS", - "accountName": "aws account 4", - "serviceName": "ec2", - "kilowattHours": 50.00593572462313, - "co2e": 0.016110262294594658, - "cost": 1.9838511921282318, - "region": "us-west-1", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AWS", - "accountName": "aws account 3", - "serviceName": "rds", - "kilowattHours": 50.803461347266776, - "co2e": 0.016367198731864895, - "cost": 2.229003808663734, - "region": "us-west-2", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AWS", - "accountName": "aws account 0", - "serviceName": "lambda", - "kilowattHours": 52.62976125953012, - "co2e": 0.019950310970888823, - "cost": 1.6972395592555778, - "region": "us-east-1", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "GCP", - "accountName": "gcp account 4", - "serviceName": "computeEngine", - "kilowattHours": 64.63052755166217, - "co2e": 0.03102265322479784, - "cost": 2.30459191166866, - "region": "us-east1", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AZURE", - "accountName": "azure account 1", - "serviceName": "virtualMachines", - "kilowattHours": 53.31425378242701, - "co2e": 0.011995707101046077, - "cost": 1.6930870858502807, - "region": "UK South", - "usesAverageCPUConstant": false - } - ] - }, - { - "timestamp": "2022-06-01T06:00:00.000Z", - "serviceEstimates": [ - { - "cloudProvider": "AWS", - "accountName": "aws account 3", - "serviceName": "ebs", - "kilowattHours": 0.3793961762889928, - "co2e": 0.00014381732914969223, - "cost": 1.5660349090236223, - "region": "us-east-1", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AWS", - "accountName": "aws account 2", - "serviceName": "s3", - "kilowattHours": 0.0007881261941471649, - "co2e": 3.2361092032637913e-7, - "cost": 2.107104300712024, - "region": "us-east-2", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AWS", - "accountName": "aws account 3", - "serviceName": "ec2", - "kilowattHours": 50.4099085739953, - "co2e": 0.016240409015558344, - "cost": 1.7912671435326524, - "region": "us-west-1", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AWS", - "accountName": "aws account 1", - "serviceName": "rds", - "kilowattHours": 56.61135475563019, - "co2e": 0.01823831032755711, - "cost": 2.0547082075494254, - "region": "us-west-2", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AWS", - "accountName": "aws account 4", - "serviceName": "lambda", - "kilowattHours": 50.16319007556745, - "co2e": 0.019015310298755278, - "cost": 2.3442440999125886, - "region": "us-east-1", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "GCP", - "accountName": "gcp account 1", - "serviceName": "computeEngine", - "kilowattHours": 50.2235650164992, - "co2e": 0.02410731120791962, - "cost": 1.6300863324887378, - "region": "us-east1", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AZURE", - "accountName": "azure account 3", - "serviceName": "virtualMachines", - "kilowattHours": 50.27485053251475, - "co2e": 0.011311841369815818, - "cost": 1.7666414199697307, - "region": "UK South", - "usesAverageCPUConstant": false - } - ] - }, - { - "timestamp": "2022-05-01T06:00:00.000Z", - "serviceEstimates": [ - { - "cloudProvider": "AWS", - "accountName": "aws account 3", - "serviceName": "ebs", - "kilowattHours": 0.0025963667867079376, - "co2e": 9.842021614705911e-7, - "cost": 2.110850189120251, - "region": "us-east-1", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AWS", - "accountName": "aws account 3", - "serviceName": "s3", - "kilowattHours": 0.00038988295545011175, - "co2e": 1.600890605714595e-7, - "cost": 1.750590801038299, - "region": "us-east-2", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AWS", - "accountName": "aws account 4", - "serviceName": "ec2", - "kilowattHours": 50.58419719553395, - "co2e": 0.016296559057893588, - "cost": 2.331961666410219, - "region": "us-west-1", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AWS", - "accountName": "aws account 1", - "serviceName": "rds", - "kilowattHours": 50.09219672002729, - "co2e": 0.01613805274070103, - "cost": 2.171231385841728, - "region": "us-west-2", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AWS", - "accountName": "aws account 0", - "serviceName": "lambda", - "kilowattHours": 50.34788323768369, - "co2e": 0.01908532175102552, - "cost": 2.178860527900543, - "region": "us-east-1", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "GCP", - "accountName": "gcp account 2", - "serviceName": "computeEngine", - "kilowattHours": 50.876621782936894, - "co2e": 0.02442077845580971, - "cost": 2.008009566294815, - "region": "us-east1", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AZURE", - "accountName": "azure account 2", - "serviceName": "virtualMachines", - "kilowattHours": 69.14280006058016, - "co2e": 0.015557130013630535, - "cost": 2.159049075087805, - "region": "UK South", - "usesAverageCPUConstant": false - } - ] - }, - { - "timestamp": "2022-04-01T06:00:00.000Z", - "serviceEstimates": [ - { - "cloudProvider": "AWS", - "accountName": "aws account 3", - "serviceName": "ebs", - "kilowattHours": 0.847821319053617, - "co2e": 0.00032138277959233556, - "cost": 1.8756121359502171, - "region": "us-east-1", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AWS", - "accountName": "aws account 3", - "serviceName": "s3", - "kilowattHours": 0.000630093974723783, - "co2e": 2.587216267733831e-7, - "cost": 2.3698572941873914, - "region": "us-east-2", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AWS", - "accountName": "aws account 3", - "serviceName": "ec2", - "kilowattHours": 50.10307382046631, - "co2e": 0.016141556983518168, - "cost": 2.351022985532233, - "region": "us-west-1", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AWS", - "accountName": "aws account 2", - "serviceName": "rds", - "kilowattHours": 50.22293041559674, - "co2e": 0.016180170823201556, - "cost": 1.5327957463127884, - "region": "us-west-2", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AWS", - "accountName": "aws account 3", - "serviceName": "lambda", - "kilowattHours": 68.18586584927158, - "co2e": 0.025847147981617528, - "cost": 2.205957045144824, - "region": "us-east-1", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "GCP", - "accountName": "gcp account 4", - "serviceName": "computeEngine", - "kilowattHours": 68.47759811709409, - "co2e": 0.032869247096205166, - "cost": 2.026647361534485, - "region": "us-east1", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AZURE", - "accountName": "azure account 1", - "serviceName": "virtualMachines", - "kilowattHours": 50.5125618456214, - "co2e": 0.011365326415264814, - "cost": 1.9833785822656285, - "region": "UK South", - "usesAverageCPUConstant": false - } - ] - }, - { - "timestamp": "2022-03-01T07:00:00.000Z", - "serviceEstimates": [ - { - "cloudProvider": "AWS", - "accountName": "aws account 1", - "serviceName": "ebs", - "kilowattHours": 0.9119831149849784, - "co2e": 0.00034570452741424076, - "cost": 1.7466850895849164, - "region": "us-east-1", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AWS", - "accountName": "aws account 1", - "serviceName": "s3", - "kilowattHours": 0.00010168764085273696, - "co2e": 4.175375883526062e-8, - "cost": 2.0929755865584747, - "region": "us-east-2", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AWS", - "accountName": "aws account 2", - "serviceName": "ec2", - "kilowattHours": 50.13033413155082, - "co2e": 0.01615033935615933, - "cost": 2.030781896188273, - "region": "us-west-1", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AWS", - "accountName": "aws account 3", - "serviceName": "rds", - "kilowattHours": 53.81264913939785, - "co2e": 0.017336659735292387, - "cost": 1.7863504430322306, - "region": "us-west-2", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AWS", - "accountName": "aws account 4", - "serviceName": "lambda", - "kilowattHours": 63.45075892152083, - "co2e": 0.02405221573362198, - "cost": 1.8740007850011755, - "region": "us-east-1", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "GCP", - "accountName": "gcp account 4", - "serviceName": "computeEngine", - "kilowattHours": 50.77403726191187, - "co2e": 0.0243715378857177, - "cost": 1.7839515021969587, - "region": "us-east1", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AZURE", - "accountName": "azure account 4", - "serviceName": "virtualMachines", - "kilowattHours": 50.373127573933836, - "co2e": 0.011333953704135112, - "cost": 1.8199290662411896, - "region": "UK South", - "usesAverageCPUConstant": false - } - ] - }, - { - "timestamp": "2022-02-01T07:00:00.000Z", - "serviceEstimates": [ - { - "cloudProvider": "AWS", - "accountName": "aws account 1", - "serviceName": "ebs", - "kilowattHours": 0.8148394263939371, - "co2e": 0.00030888036652372336, - "cost": 1.9952062784974551, - "region": "us-east-1", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AWS", - "accountName": "aws account 1", - "serviceName": "s3", - "kilowattHours": 0.00010100550904187954, - "co2e": 4.147367005666808e-8, - "cost": 2.479801470978538, - "region": "us-east-2", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AWS", - "accountName": "aws account 1", - "serviceName": "ec2", - "kilowattHours": 50.933103987612505, - "co2e": 0.016408965312377156, - "cost": 2.014818576889102, - "region": "us-west-1", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AWS", - "accountName": "aws account 2", - "serviceName": "rds", - "kilowattHours": 50.92736661108625, - "co2e": 0.016407116918993825, - "cost": 2.0552869714092585, - "region": "us-west-2", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AWS", - "accountName": "aws account 2", - "serviceName": "lambda", - "kilowattHours": 70.47173348463286, - "co2e": 0.026713649540286294, - "cost": 1.8418849726705238, - "region": "us-east-1", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "GCP", - "accountName": "gcp account 3", - "serviceName": "computeEngine", - "kilowattHours": 57.666701915218034, - "co2e": 0.02768001691930466, - "cost": 1.611910606014586, - "region": "us-east1", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AZURE", - "accountName": "azure account 2", - "serviceName": "virtualMachines", - "kilowattHours": 50.773157853289746, - "co2e": 0.011423960516990192, - "cost": 2.2728625664822433, - "region": "UK South", - "usesAverageCPUConstant": false - } - ] - } - ], - "recommendations": [ - { - "cloudProvider": "AWS", - "accountId": "aws account 0", - "accountName": "aws account 0", - "region": "us-west-1", - "recommendationType": "Modify", - "instanceName": "example-instance-5", - "recommendationDetail": "Modify instance: example-instance-5.", - "resourceId": "i-0f12345678912b12I", - "kilowattHourSavings": 116.513, - "costSavings": 3.611, - "co2eSavings": 0.037536643671 - }, - { - "cloudProvider": "AWS", - "accountId": "aws account 1", - "accountName": "aws account 1", - "region": "us-east-2", - "recommendationType": "Modify", - "instanceName": "example-instance", - "recommendationDetail": "Modify instance: example-instance.", - "resourceId": "i-0f12345678912b12I", - "kilowattHourSavings": 114.978, - "costSavings": 13.506, - "co2eSavings": 0.047210886624 - }, - { - "cloudProvider": "AWS", - "accountId": "aws account 2", - "accountName": "aws account 2", - "region": "us-east-1", - "recommendationType": "Modify", - "instanceName": "example-instance-2", - "recommendationDetail": "Modify instance: example-instance-2.", - "resourceId": "i-0f12345678912b12I", - "kilowattHourSavings": 18.419, - "costSavings": 5.667, - "co2eSavings": 0.0069820719110000005 - }, - { - "cloudProvider": "AWS", - "accountId": "aws account 3", - "accountName": "aws account 3", - "region": "us-west-2", - "recommendationType": "Delete", - "instanceName": "example-instance", - "recommendationDetail": "Delete instance: example-instance.", - "resourceId": "i-0f12345678912b12I", - "kilowattHourSavings": 11.195, - "costSavings": 4.442, - "co2eSavings": 0.003606659565 - }, - { - "cloudProvider": "AWS", - "accountId": "aws account 4", - "accountName": "aws account 4", - "region": "us-west-1", - "recommendationType": "Delete", - "instanceName": "example-instance", - "recommendationDetail": "Delete instance: example-instance.", - "resourceId": "i-0f12345678912b12I", - "kilowattHourSavings": 111.717, - "costSavings": 5.788, - "co2eSavings": 0.035991530739 - }, - { - "cloudProvider": "GCP", - "accountId": "gcp account 0", - "accountName": "gcp account 0", - "region": "us-west1", - "recommendationType": "DELETE_IMAGE", - "instanceName": "test-instance-1", - "recommendationDetail": "Save cost by performing a DELETE_IMAGE for instance: test-instance-1.", - "resourceId": 6906976106869124000, - "kilowattHourSavings": 12.081, - "costSavings": 1.314, - "co2eSavings": 0.0009423179999999999 - }, - { - "cloudProvider": "GCP", - "accountId": "gcp account 1", - "accountName": "gcp account 1", - "region": "us-west1", - "recommendationType": "SNAPSHOT_AND_DELETE_DISK", - "instanceName": "test-instance-3", - "recommendationDetail": "Save cost by performing a SNAPSHOT_AND_DELETE_DISK for instance: test-instance-3.", - "resourceId": 4256745502855943000, - "kilowattHourSavings": 18.742, - "costSavings": 4.549, - "co2eSavings": 0.001461876 - }, - { - "cloudProvider": "GCP", - "accountId": "gcp account 2", - "accountName": "gcp account 2", - "region": "us-west1", - "recommendationType": "CHANGE_MACHINE_TYPE", - "instanceName": "test-instance-10", - "recommendationDetail": "Save cost by performing a CHANGE_MACHINE_TYPE for instance: test-instance-10.", - "resourceId": 9625521363699055000, - "kilowattHourSavings": 16.989, - "costSavings": 8.165, - "co2eSavings": 0.001325142 - }, - { - "cloudProvider": "GCP", - "accountId": "gcp account 3", - "accountName": "gcp account 3", - "region": "us-east1", - "recommendationType": "DELETE_ADDRESS", - "instanceName": "test-instance-8", - "recommendationDetail": "Save cost by performing a DELETE_ADDRESS for instance: test-instance-8.", - "resourceId": 9351508929180877000, - "kilowattHourSavings": 19.946, - "costSavings": 5.2, - "co2eSavings": 0.00957408 - }, - { - "cloudProvider": "GCP", - "accountId": "gcp account 4", - "accountName": "gcp account 4", - "region": "us-west2", - "recommendationType": "DELETE_DISK", - "instanceName": "test-instance-14", - "recommendationDetail": "Save cost by performing a DELETE_DISK for instance: test-instance-14.", - "resourceId": 7840914330904416000, - "kilowattHourSavings": 18.782, - "costSavings": 8.359, - "co2eSavings": 0.0047518460000000005 - }, - { - "cloudProvider": "GCP", - "accountId": "gcp account 2", - "accountName": "gcp account 2", - "region": "us-east1", - "recommendationType": "STOP_VM", - "instanceName": "test-instance-9", - "recommendationDetail": "Save cost by performing a STOP_VM for instance: test-instance-9.", - "resourceId": 8928403120086348000, - "kilowattHourSavings": 116.483, - "costSavings": 8.409, - "co2eSavings": 0.055911840000000004 - }, - { - "cloudProvider": "GCP", - "accountId": "gcp account 4", - "accountName": "gcp account 4", - "region": "us-east1", - "recommendationType": "STOP_VM", - "instanceName": "test-instance-9", - "recommendationDetail": "Save cost by performing a STOP_VM for instance: test-instance-9.", - "resourceId": 8928403120086348000, - "kilowattHourSavings": 0.0001, - "costSavings": 0.0002, - "co2eSavings": 4.8000000000000006e-8 - }, - { - "cloudProvider": "GCP", - "accountId": "gcp account 4", - "accountName": "gcp account 4", - "region": "us-east1", - "recommendationType": "STOP_VM", - "instanceName": "test-instance-9", - "recommendationDetail": "Save cost by performing a STOP_VM for instance: test-instance-9.", - "resourceId": 8928403120086348000, - "kilowattHourSavings": 0, - "costSavings": 0, - "co2eSavings": 0 - }, - { - "cloudProvider": "GCP", - "accountId": "gcp account 4", - "accountName": "gcp account 4", - "region": "us-east1", - "recommendationType": "STOP_VM", - "instanceName": "test-instance-9", - "recommendationDetail": "Save cost by performing a STOP_VM for instance: test-instance-9.", - "resourceId": 8928403120086348000, - "kilowattHourSavings": 0.001, - "costSavings": 0.001, - "co2eSavings": 4.800000000000001e-7 - }, - { - "cloudProvider": "GCP", - "accountId": "gcp account 4", - "accountName": "gcp account 4", - "region": "us-east1", - "recommendationType": "STOP_VM", - "instanceName": "test-instance-9", - "recommendationDetail": "Save cost by performing a STOP_VM for instance: test-instance-9.", - "resourceId": 8928403120086348000, - "kilowattHourSavings": 0.002, - "costSavings": 0.002, - "co2eSavings": 9.600000000000001e-7 - } - ] -} +{"emissions":[{"region":"us-east-1","mtPerKwHour":0.000379069},{"region":"us-east-2","mtPerKwHour":0.000410608},{"region":"us-west-1","mtPerKwHour":0.000322167},{"region":"us-west-2","mtPerKwHour":0.000322167},{"region":"us-gov-east-1","mtPerKwHour":0.000379069},{"region":"us-gov-west-1","mtPerKwHour":0.000322167},{"region":"af-south-1","mtPerKwHour":0.0009006},{"region":"ap-east-1","mtPerKwHour":0.00071},{"region":"ap-south-1","mtPerKwHour":0.0007082},{"region":"ap-northeast-3","mtPerKwHour":0.0004658},{"region":"ap-northeast-2","mtPerKwHour":0.0004156},{"region":"ap-southeast-1","mtPerKwHour":0.000408},{"region":"ap-southeast-2","mtPerKwHour":0.00076},{"region":"ap-northeast-1","mtPerKwHour":0.0004658},{"region":"ca-central-1","mtPerKwHour":0.00012},{"region":"cn-north-1","mtPerKwHour":0.0005374},{"region":"cn-northwest-1","mtPerKwHour":0.0005374},{"region":"eu-central-1","mtPerKwHour":0.000311},{"region":"eu-west-1","mtPerKwHour":0.0002786},{"region":"eu-west-2","mtPerKwHour":0.000225},{"region":"eu-south-1","mtPerKwHour":0.0002134},{"region":"eu-west-3","mtPerKwHour":0.0000511},{"region":"eu-north-1","mtPerKwHour":0.0000088},{"region":"me-south-1","mtPerKwHour":0.0005059},{"region":"sa-east-1","mtPerKwHour":0.0000617},{"region":"us-central1","mtPerKwHour":0.000454},{"region":"us-central2","mtPerKwHour":0.000454},{"region":"us-east1","mtPerKwHour":0.00048},{"region":"us-east4","mtPerKwHour":0.000361},{"region":"us-west1","mtPerKwHour":0.000078},{"region":"us-west2","mtPerKwHour":0.000253},{"region":"us-west3","mtPerKwHour":0.000533},{"region":"us-west4","mtPerKwHour":0.000455},{"region":"asia-east1","mtPerKwHour":0.00054},{"region":"asia-east2","mtPerKwHour":0.000453},{"region":"asia-northeast1","mtPerKwHour":0.000554},{"region":"asia-northeast2","mtPerKwHour":0.000442},{"region":"asia-northeast3","mtPerKwHour":0.000457},{"region":"asia-south1","mtPerKwHour":0.000721},{"region":"asia-southeast1","mtPerKwHour":0.000493},{"region":"asia-southeast2","mtPerKwHour":0.000647},{"region":"australia-southeast1","mtPerKwHour":0.000727},{"region":"europe-north1","mtPerKwHour":0.000133},{"region":"europe-west1","mtPerKwHour":0.000212},{"region":"europe-west2","mtPerKwHour":0.000231},{"region":"europe-west3","mtPerKwHour":0.000293},{"region":"europe-west4","mtPerKwHour":0.00041},{"region":"europe-west6","mtPerKwHour":0.000087},{"region":"northamerica-northeast1","mtPerKwHour":0.000027},{"region":"southamerica-east1","mtPerKwHour":0.000103},{"region":"AP East","mtPerKwHour":0.00071},{"region":"EU West","mtPerKwHour":0.0003284},{"region":"IN Central","mtPerKwHour":0.0007082},{"region":"UK South","mtPerKwHour":0.000225},{"region":"UK West","mtPerKwHour":0.000225},{"region":"US Central","mtPerKwHour":0.000426254},{"region":"US East","mtPerKwHour":0.000379069},{"region":"US South Central","mtPerKwHour":0.000373231},{"region":"US West","mtPerKwHour":0.000322167},{"region":"US West 2","mtPerKwHour":0.000322167},{"region":"Unknown","mtPerKwHour":0.0003852304903666667}],"footprint":[{"timestamp":"2023-05-01T06:00:00.000Z","serviceEstimates":[{"cloudProvider":"AWS","accountName":"aws account 0","serviceName":"ebs","kilowattHours":0.9099318685999922,"co2e":0.00034492696349833045,"cost":2.0611247334106126,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 0","serviceName":"s3","kilowattHours":0.000986946262424086,"co2e":4.0524803092142913e-7,"cost":2.341122015683861,"region":"us-east-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"ec2","kilowattHours":50.176579830111365,"co2e":0.016165238194127487,"cost":2.4154850519158613,"region":"us-west-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"rds","kilowattHours":50.15554852182787,"co2e":0.01615846260063172,"cost":1.8825350541114,"region":"us-west-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 0","serviceName":"lambda","kilowattHours":72.26453941514701,"co2e":0.027393246691560364,"cost":2.0084835420769656,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"GCP","accountName":"gcp account 4","serviceName":"computeEngine","kilowattHours":68.54797801901817,"co2e":0.03290302944912873,"cost":1.8688785461442674,"region":"us-east1","usesAverageCPUConstant":false},{"cloudProvider":"AZURE","accountName":"azure account 4","serviceName":"virtualMachines","kilowattHours":50.02721869884774,"co2e":0.011256124207240741,"cost":2.2910467204304323,"region":"UK South","usesAverageCPUConstant":false}]},{"timestamp":"2023-04-01T06:00:00.000Z","serviceEstimates":[{"cloudProvider":"AWS","accountName":"aws account 0","serviceName":"ebs","kilowattHours":0.9281382786569483,"co2e":0.00035182844915221075,"cost":2.225771432143803,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"s3","kilowattHours":0.00003270772530525257,"co2e":1.3430053672139147e-8,"cost":1.5028221506352941,"region":"us-east-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"ec2","kilowattHours":50.558103056454314,"co2e":0.016288152387388715,"cost":2.440745140900739,"region":"us-west-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"rds","kilowattHours":62.615457571422965,"co2e":0.02017263411941262,"cost":1.5955486508938537,"region":"us-west-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 0","serviceName":"lambda","kilowattHours":50.903421738830794,"co2e":0.01929590917511685,"cost":2.238206567880293,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"GCP","accountName":"gcp account 1","serviceName":"computeEngine","kilowattHours":65.28200479098749,"co2e":0.0050919963736970235,"cost":1.6177172869839958,"region":"us-west1","usesAverageCPUConstant":false},{"cloudProvider":"AZURE","accountName":"azure account 1","serviceName":"virtualMachines","kilowattHours":61.90994721222564,"co2e":0.013929738122750768,"cost":2.2081554041299234,"region":"UK South","usesAverageCPUConstant":false}]},{"timestamp":"2023-03-01T07:00:00.000Z","serviceEstimates":[{"cloudProvider":"AWS","accountName":"aws account 1","serviceName":"ebs","kilowattHours":0.9290466782034781,"co2e":0.0003521727952599142,"cost":1.9170203054445436,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 1","serviceName":"s3","kilowattHours":0.00007414638904524828,"co2e":3.0445100513091304e-8,"cost":2.3496108515083787,"region":"us-east-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"ec2","kilowattHours":50.74719344234976,"co2e":0.016349071069741494,"cost":1.8854738557659787,"region":"us-west-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 4","serviceName":"rds","kilowattHours":62.70792733734538,"co2e":0.02020242482649055,"cost":1.9619661070136278,"region":"us-west-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 2","serviceName":"lambda","kilowattHours":50.18018466997432,"co2e":0.019021752422662495,"cost":1.7366812626585235,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"GCP","accountName":"gcp account 3","serviceName":"computeEngine","kilowattHours":50.642678514609926,"co2e":0.024308485687012764,"cost":2.112304415112801,"region":"us-east1","usesAverageCPUConstant":false},{"cloudProvider":"AZURE","accountName":"azure account 1","serviceName":"virtualMachines","kilowattHours":50.39790599515025,"co2e":0.011339528848908806,"cost":1.844015443986478,"region":"UK South","usesAverageCPUConstant":false}]},{"timestamp":"2023-02-01T07:00:00.000Z","serviceEstimates":[{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"ebs","kilowattHours":0.7903104123827822,"co2e":0.0002995821777115289,"cost":1.5941517912292598,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"s3","kilowattHours":0.0008485340026475967,"co2e":3.484148497591244e-7,"cost":2.422188298668747,"region":"us-east-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 2","serviceName":"ec2","kilowattHours":50.571988266018494,"co2e":0.01629262574369838,"cost":1.813087747561918,"region":"us-west-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"rds","kilowattHours":50.54507810450079,"co2e":0.016283956177692707,"cost":2.3595674179586856,"region":"us-west-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 4","serviceName":"lambda","kilowattHours":50.31390279587026,"co2e":0.019072440818927745,"cost":1.6209696239449094,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"GCP","accountName":"gcp account 2","serviceName":"computeEngine","kilowattHours":50.995343212272175,"co2e":0.01290182183270486,"cost":2.291652099168084,"region":"us-west2","usesAverageCPUConstant":false},{"cloudProvider":"AZURE","accountName":"azure account 1","serviceName":"virtualMachines","kilowattHours":64.71563943290937,"co2e":0.014561018872404607,"cost":2.1519820863003964,"region":"UK South","usesAverageCPUConstant":false}]},{"timestamp":"2023-01-01T07:00:00.000Z","serviceEstimates":[{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"ebs","kilowattHours":0.616242116061845,"co2e":0.00023359828269344752,"cost":1.7611637388166252,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 2","serviceName":"s3","kilowattHours":0.00009916949627876193,"co2e":4.071978852802988e-8,"cost":2.4426180887131026,"region":"us-east-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 2","serviceName":"ec2","kilowattHours":50.149667680040125,"co2e":0.016156567987475487,"cost":2.2645178139778706,"region":"us-west-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 4","serviceName":"rds","kilowattHours":50.15605062459718,"co2e":0.0161586243615746,"cost":2.345203244211236,"region":"us-west-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 2","serviceName":"lambda","kilowattHours":55.97282211541847,"co2e":0.021217561706469563,"cost":1.6084626580032653,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"GCP","accountName":"gcp account 0","serviceName":"computeEngine","kilowattHours":50.58370656686491,"co2e":0.02428017915209516,"cost":2.002632458514051,"region":"us-east1","usesAverageCPUConstant":false},{"cloudProvider":"AZURE","accountName":"azure account 2","serviceName":"virtualMachines","kilowattHours":50.91095442793027,"co2e":0.01145496474628431,"cost":1.9447149842967881,"region":"UK South","usesAverageCPUConstant":false}]},{"timestamp":"2022-12-01T07:00:00.000Z","serviceEstimates":[{"cloudProvider":"AWS","accountName":"aws account 2","serviceName":"ebs","kilowattHours":0.8445236465308483,"co2e":0.00032013273416680216,"cost":1.8371062169192403,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 2","serviceName":"s3","kilowattHours":0.0002648754924918617,"co2e":1.0875999622109836e-7,"cost":2.236483110571217,"region":"us-east-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"ec2","kilowattHours":50.37629287099671,"co2e":0.016229579145370397,"cost":2.449362850757937,"region":"us-west-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"rds","kilowattHours":50.52526081119707,"co2e":0.016277571699760927,"cost":2.302982452488826,"region":"us-west-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"lambda","kilowattHours":50.357072062609866,"co2e":0.01908880494970146,"cost":2.3300950152526614,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"GCP","accountName":"gcp account 0","serviceName":"computeEngine","kilowattHours":53.70289639150512,"co2e":0.02577739026792246,"cost":1.8981217535028845,"region":"us-east1","usesAverageCPUConstant":false},{"cloudProvider":"AZURE","accountName":"azure account 4","serviceName":"virtualMachines","kilowattHours":50.82389577797691,"co2e":0.011435376550044804,"cost":1.5108425566125716,"region":"UK South","usesAverageCPUConstant":false}]},{"timestamp":"2022-11-01T06:00:00.000Z","serviceEstimates":[{"cloudProvider":"AWS","accountName":"aws account 0","serviceName":"ebs","kilowattHours":0.33278898489743725,"co2e":0.00012614998771608664,"cost":2.201012388385645,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 1","serviceName":"s3","kilowattHours":0.00098701927251199,"co2e":4.052780094476032e-7,"cost":1.645103657701107,"region":"us-east-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 4","serviceName":"ec2","kilowattHours":50.904310297531644,"co2e":0.016399688935624875,"cost":2.235818514499144,"region":"us-west-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"rds","kilowattHours":50.12852654254913,"co2e":0.016149757010633425,"cost":1.5786907055484218,"region":"us-west-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 2","serviceName":"lambda","kilowattHours":59.54349975897656,"co2e":0.022571094910135484,"cost":2.324453671310117,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"GCP","accountName":"gcp account 3","serviceName":"computeEngine","kilowattHours":63.69530490695243,"co2e":0.03057374635533717,"cost":2.4104193295098923,"region":"us-east1","usesAverageCPUConstant":false},{"cloudProvider":"AZURE","accountName":"azure account 0","serviceName":"virtualMachines","kilowattHours":65.60925208439582,"co2e":0.014762081718989059,"cost":2.4070137425368685,"region":"UK South","usesAverageCPUConstant":false}]},{"timestamp":"2022-10-01T06:00:00.000Z","serviceEstimates":[{"cloudProvider":"AWS","accountName":"aws account 4","serviceName":"ebs","kilowattHours":0.7421818551593697,"co2e":0.00028133813365340714,"cost":2.498136748374863,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 1","serviceName":"s3","kilowattHours":0.0002180651158962681,"co2e":8.953928110793486e-8,"cost":1.7632736153406632,"region":"us-east-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 0","serviceName":"ec2","kilowattHours":54.239336047864754,"co2e":0.017474124176532442,"cost":1.9661288302344184,"region":"us-west-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 0","serviceName":"rds","kilowattHours":50.23120235816861,"co2e":0.016182835770124106,"cost":2.0142111162898986,"region":"us-west-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"lambda","kilowattHours":50.86615400739505,"co2e":0.019281782133429234,"cost":2.164622206937599,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"GCP","accountName":"gcp account 3","serviceName":"computeEngine","kilowattHours":59.19683162717123,"co2e":0.02841447918104219,"cost":1.6906306697783389,"region":"us-east1","usesAverageCPUConstant":false},{"cloudProvider":"AZURE","accountName":"azure account 2","serviceName":"virtualMachines","kilowattHours":50.27713613081028,"co2e":0.011312355629432311,"cost":1.7863670364270168,"region":"UK South","usesAverageCPUConstant":false}]},{"timestamp":"2022-09-01T06:00:00.000Z","serviceEstimates":[{"cloudProvider":"AWS","accountName":"aws account 0","serviceName":"ebs","kilowattHours":0.5648293345133781,"co2e":0.00021410929100465172,"cost":2.3304265345916653,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 4","serviceName":"s3","kilowattHours":0.00005263625919629122,"co2e":2.161286911607075e-8,"cost":1.6038362843689178,"region":"us-east-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 0","serviceName":"ec2","kilowattHours":64.1169430041115,"co2e":0.02065636317680559,"cost":1.8957903016901754,"region":"us-west-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 1","serviceName":"rds","kilowattHours":50.69439982629539,"co2e":0.016332062708838108,"cost":2.3649041975938676,"region":"us-west-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 4","serviceName":"lambda","kilowattHours":50.69873776324742,"co2e":0.01921831982517644,"cost":2.1212012771669846,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"GCP","accountName":"gcp account 2","serviceName":"computeEngine","kilowattHours":50.71620115791394,"co2e":0.024343776555798693,"cost":2.0052165772682793,"region":"us-east1","usesAverageCPUConstant":false},{"cloudProvider":"AZURE","accountName":"azure account 0","serviceName":"virtualMachines","kilowattHours":50.146548832423804,"co2e":0.011282973487295355,"cost":2.4727258099898037,"region":"UK South","usesAverageCPUConstant":false}]},{"timestamp":"2022-08-01T06:00:00.000Z","serviceEstimates":[{"cloudProvider":"AWS","accountName":"aws account 0","serviceName":"ebs","kilowattHours":0.35269028620932774,"co2e":0.00013369395410308364,"cost":2.426536030208615,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 2","serviceName":"s3","kilowattHours":0.00007772936246220752,"co2e":3.1916298061882105e-8,"cost":1.9926068310477163,"region":"us-east-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 4","serviceName":"ec2","kilowattHours":50.00593572462313,"co2e":0.016110262294594658,"cost":1.9838511921282318,"region":"us-west-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"rds","kilowattHours":50.803461347266776,"co2e":0.016367198731864895,"cost":2.229003808663734,"region":"us-west-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 0","serviceName":"lambda","kilowattHours":52.62976125953012,"co2e":0.019950310970888823,"cost":1.6972395592555778,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"GCP","accountName":"gcp account 4","serviceName":"computeEngine","kilowattHours":64.63052755166217,"co2e":0.03102265322479784,"cost":2.30459191166866,"region":"us-east1","usesAverageCPUConstant":false},{"cloudProvider":"AZURE","accountName":"azure account 1","serviceName":"virtualMachines","kilowattHours":53.31425378242701,"co2e":0.011995707101046077,"cost":1.6930870858502807,"region":"UK South","usesAverageCPUConstant":false}]},{"timestamp":"2022-07-01T06:00:00.000Z","serviceEstimates":[{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"ebs","kilowattHours":0.3793961762889928,"co2e":0.00014381732914969223,"cost":1.5660349090236223,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 2","serviceName":"s3","kilowattHours":0.0007881261941471649,"co2e":3.2361092032637913e-7,"cost":2.107104300712024,"region":"us-east-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"ec2","kilowattHours":50.4099085739953,"co2e":0.016240409015558344,"cost":1.7912671435326524,"region":"us-west-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 1","serviceName":"rds","kilowattHours":56.61135475563019,"co2e":0.01823831032755711,"cost":2.0547082075494254,"region":"us-west-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 4","serviceName":"lambda","kilowattHours":50.16319007556745,"co2e":0.019015310298755278,"cost":2.3442440999125886,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"GCP","accountName":"gcp account 1","serviceName":"computeEngine","kilowattHours":50.2235650164992,"co2e":0.02410731120791962,"cost":1.6300863324887378,"region":"us-east1","usesAverageCPUConstant":false},{"cloudProvider":"AZURE","accountName":"azure account 3","serviceName":"virtualMachines","kilowattHours":50.27485053251475,"co2e":0.011311841369815818,"cost":1.7666414199697307,"region":"UK South","usesAverageCPUConstant":false}]},{"timestamp":"2022-06-01T06:00:00.000Z","serviceEstimates":[{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"ebs","kilowattHours":0.0025963667867079376,"co2e":9.842021614705911e-7,"cost":2.110850189120251,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"s3","kilowattHours":0.00038988295545011175,"co2e":1.600890605714595e-7,"cost":1.750590801038299,"region":"us-east-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 4","serviceName":"ec2","kilowattHours":50.58419719553395,"co2e":0.016296559057893588,"cost":2.331961666410219,"region":"us-west-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 1","serviceName":"rds","kilowattHours":50.09219672002729,"co2e":0.01613805274070103,"cost":2.171231385841728,"region":"us-west-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 0","serviceName":"lambda","kilowattHours":50.34788323768369,"co2e":0.01908532175102552,"cost":2.178860527900543,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"GCP","accountName":"gcp account 2","serviceName":"computeEngine","kilowattHours":50.876621782936894,"co2e":0.02442077845580971,"cost":2.008009566294815,"region":"us-east1","usesAverageCPUConstant":false},{"cloudProvider":"AZURE","accountName":"azure account 2","serviceName":"virtualMachines","kilowattHours":69.14280006058016,"co2e":0.015557130013630535,"cost":2.159049075087805,"region":"UK South","usesAverageCPUConstant":false}]},{"timestamp":"2022-05-01T06:00:00.000Z","serviceEstimates":[{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"ebs","kilowattHours":0.847821319053617,"co2e":0.00032138277959233556,"cost":1.8756121359502171,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"s3","kilowattHours":0.000630093974723783,"co2e":2.587216267733831e-7,"cost":2.3698572941873914,"region":"us-east-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"ec2","kilowattHours":50.10307382046631,"co2e":0.016141556983518168,"cost":2.351022985532233,"region":"us-west-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 2","serviceName":"rds","kilowattHours":50.22293041559674,"co2e":0.016180170823201556,"cost":1.5327957463127884,"region":"us-west-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"lambda","kilowattHours":68.18586584927158,"co2e":0.025847147981617528,"cost":2.205957045144824,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"GCP","accountName":"gcp account 4","serviceName":"computeEngine","kilowattHours":68.47759811709409,"co2e":0.032869247096205166,"cost":2.026647361534485,"region":"us-east1","usesAverageCPUConstant":false},{"cloudProvider":"AZURE","accountName":"azure account 1","serviceName":"virtualMachines","kilowattHours":50.5125618456214,"co2e":0.011365326415264814,"cost":1.9833785822656285,"region":"UK South","usesAverageCPUConstant":false}]},{"timestamp":"2022-04-01T06:00:00.000Z","serviceEstimates":[{"cloudProvider":"AWS","accountName":"aws account 1","serviceName":"ebs","kilowattHours":0.9119831149849784,"co2e":0.00034570452741424076,"cost":1.7466850895849164,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 1","serviceName":"s3","kilowattHours":0.00010168764085273696,"co2e":4.175375883526062e-8,"cost":2.0929755865584747,"region":"us-east-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 2","serviceName":"ec2","kilowattHours":50.13033413155082,"co2e":0.01615033935615933,"cost":2.030781896188273,"region":"us-west-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"rds","kilowattHours":53.81264913939785,"co2e":0.017336659735292387,"cost":1.7863504430322306,"region":"us-west-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 4","serviceName":"lambda","kilowattHours":63.45075892152083,"co2e":0.02405221573362198,"cost":1.8740007850011755,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"GCP","accountName":"gcp account 4","serviceName":"computeEngine","kilowattHours":50.77403726191187,"co2e":0.0243715378857177,"cost":1.7839515021969587,"region":"us-east1","usesAverageCPUConstant":false},{"cloudProvider":"AZURE","accountName":"azure account 4","serviceName":"virtualMachines","kilowattHours":50.373127573933836,"co2e":0.011333953704135112,"cost":1.8199290662411896,"region":"UK South","usesAverageCPUConstant":false}]},{"timestamp":"2022-03-01T07:00:00.000Z","serviceEstimates":[{"cloudProvider":"AWS","accountName":"aws account 1","serviceName":"ebs","kilowattHours":0.8148394263939371,"co2e":0.00030888036652372336,"cost":1.9952062784974551,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 1","serviceName":"s3","kilowattHours":0.00010100550904187954,"co2e":4.147367005666808e-8,"cost":2.479801470978538,"region":"us-east-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 1","serviceName":"ec2","kilowattHours":50.933103987612505,"co2e":0.016408965312377156,"cost":2.014818576889102,"region":"us-west-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 2","serviceName":"rds","kilowattHours":50.92736661108625,"co2e":0.016407116918993825,"cost":2.0552869714092585,"region":"us-west-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 2","serviceName":"lambda","kilowattHours":70.47173348463286,"co2e":0.026713649540286294,"cost":1.8418849726705238,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"GCP","accountName":"gcp account 3","serviceName":"computeEngine","kilowattHours":57.666701915218034,"co2e":0.02768001691930466,"cost":1.611910606014586,"region":"us-east1","usesAverageCPUConstant":false},{"cloudProvider":"AZURE","accountName":"azure account 2","serviceName":"virtualMachines","kilowattHours":50.773157853289746,"co2e":0.011423960516990192,"cost":2.2728625664822433,"region":"UK South","usesAverageCPUConstant":false}]}],"recommendations":[{"cloudProvider":"AWS","accountId":"aws account 0","accountName":"aws account 0","region":"us-west-1","recommendationType":"Modify","instanceName":"example-instance-5","recommendationDetail":"Modify instance: example-instance-5.","resourceId":"i-0f12345678912b12I","kilowattHourSavings":116.513,"costSavings":3.611,"co2eSavings":0.037536643671},{"cloudProvider":"AWS","accountId":"aws account 1","accountName":"aws account 1","region":"us-east-2","recommendationType":"Modify","instanceName":"example-instance","recommendationDetail":"Modify instance: example-instance.","resourceId":"i-0f12345678912b12I","kilowattHourSavings":114.978,"costSavings":13.506,"co2eSavings":0.047210886624},{"cloudProvider":"AWS","accountId":"aws account 2","accountName":"aws account 2","region":"us-east-1","recommendationType":"Modify","instanceName":"example-instance-2","recommendationDetail":"Modify instance: example-instance-2.","resourceId":"i-0f12345678912b12I","kilowattHourSavings":18.419,"costSavings":5.667,"co2eSavings":0.0069820719110000005},{"cloudProvider":"AWS","accountId":"aws account 3","accountName":"aws account 3","region":"us-west-2","recommendationType":"Delete","instanceName":"example-instance","recommendationDetail":"Delete instance: example-instance.","resourceId":"i-0f12345678912b12I","kilowattHourSavings":11.195,"costSavings":4.442,"co2eSavings":0.003606659565},{"cloudProvider":"AWS","accountId":"aws account 4","accountName":"aws account 4","region":"us-west-1","recommendationType":"Delete","instanceName":"example-instance","recommendationDetail":"Delete instance: example-instance.","resourceId":"i-0f12345678912b12I","kilowattHourSavings":111.717,"costSavings":5.788,"co2eSavings":0.035991530739},{"cloudProvider":"GCP","accountId":"gcp account 0","accountName":"gcp account 0","region":"us-west1","recommendationType":"DELETE_IMAGE","instanceName":"test-instance-1","recommendationDetail":"Save cost by performing a DELETE_IMAGE for instance: test-instance-1.","resourceId":6906976106869124000,"kilowattHourSavings":12.081,"costSavings":1.314,"co2eSavings":0.0009423179999999999},{"cloudProvider":"GCP","accountId":"gcp account 1","accountName":"gcp account 1","region":"us-west1","recommendationType":"SNAPSHOT_AND_DELETE_DISK","instanceName":"test-instance-3","recommendationDetail":"Save cost by performing a SNAPSHOT_AND_DELETE_DISK for instance: test-instance-3.","resourceId":4256745502855943000,"kilowattHourSavings":18.742,"costSavings":4.549,"co2eSavings":0.001461876},{"cloudProvider":"GCP","accountId":"gcp account 2","accountName":"gcp account 2","region":"us-west1","recommendationType":"CHANGE_MACHINE_TYPE","instanceName":"test-instance-10","recommendationDetail":"Save cost by performing a CHANGE_MACHINE_TYPE for instance: test-instance-10.","resourceId":9625521363699055000,"kilowattHourSavings":16.989,"costSavings":8.165,"co2eSavings":0.001325142},{"cloudProvider":"GCP","accountId":"gcp account 3","accountName":"gcp account 3","region":"us-east1","recommendationType":"DELETE_ADDRESS","instanceName":"test-instance-8","recommendationDetail":"Save cost by performing a DELETE_ADDRESS for instance: test-instance-8.","resourceId":9351508929180877000,"kilowattHourSavings":19.946,"costSavings":5.2,"co2eSavings":0.00957408},{"cloudProvider":"GCP","accountId":"gcp account 4","accountName":"gcp account 4","region":"us-west2","recommendationType":"DELETE_DISK","instanceName":"test-instance-14","recommendationDetail":"Save cost by performing a DELETE_DISK for instance: test-instance-14.","resourceId":7840914330904416000,"kilowattHourSavings":18.782,"costSavings":8.359,"co2eSavings":0.0047518460000000005},{"cloudProvider":"GCP","accountId":"gcp account 2","accountName":"gcp account 2","region":"us-east1","recommendationType":"STOP_VM","instanceName":"test-instance-9","recommendationDetail":"Save cost by performing a STOP_VM for instance: test-instance-9.","resourceId":8928403120086348000,"kilowattHourSavings":116.483,"costSavings":8.409,"co2eSavings":0.055911840000000004},{"cloudProvider":"GCP","accountId":"gcp account 4","accountName":"gcp account 4","region":"us-east1","recommendationType":"STOP_VM","instanceName":"test-instance-9","recommendationDetail":"Save cost by performing a STOP_VM for instance: test-instance-9.","resourceId":8928403120086348000,"kilowattHourSavings":0.0001,"costSavings":0.0002,"co2eSavings":4.8000000000000006e-8},{"cloudProvider":"GCP","accountId":"gcp account 4","accountName":"gcp account 4","region":"us-east1","recommendationType":"STOP_VM","instanceName":"test-instance-9","recommendationDetail":"Save cost by performing a STOP_VM for instance: test-instance-9.","resourceId":8928403120086348000,"kilowattHourSavings":0,"costSavings":0,"co2eSavings":0},{"cloudProvider":"GCP","accountId":"gcp account 4","accountName":"gcp account 4","region":"us-east1","recommendationType":"STOP_VM","instanceName":"test-instance-9","recommendationDetail":"Save cost by performing a STOP_VM for instance: test-instance-9.","resourceId":8928403120086348000,"kilowattHourSavings":0.001,"costSavings":0.001,"co2eSavings":4.800000000000001e-7},{"cloudProvider":"GCP","accountId":"gcp account 4","accountName":"gcp account 4","region":"us-east1","recommendationType":"STOP_VM","instanceName":"test-instance-9","recommendationDetail":"Save cost by performing a STOP_VM for instance: test-instance-9.","resourceId":8928403120086348000,"kilowattHourSavings":0.002,"costSavings":0.002,"co2eSavings":9.600000000000001e-7}]} \ No newline at end of file diff --git a/packages/create-app/templates/default-app/packages/client/stub-server/mockData.json b/packages/create-app/templates/default-app/packages/client/stub-server/mockData.json index 1ceac5f79..7f1a76b4b 100644 --- a/packages/create-app/templates/default-app/packages/client/stub-server/mockData.json +++ b/packages/create-app/templates/default-app/packages/client/stub-server/mockData.json @@ -64,7 +64,7 @@ ], "footprint": [ { - "timestamp": "2023-04-01T06:00:00.000Z", + "timestamp": "2023-05-01T06:00:00.000Z", "serviceEstimates": [ { "cloudProvider": "AWS", @@ -139,7 +139,7 @@ ] }, { - "timestamp": "2023-03-01T07:00:00.000Z", + "timestamp": "2023-04-01T06:00:00.000Z", "serviceEstimates": [ { "cloudProvider": "AWS", @@ -214,7 +214,7 @@ ] }, { - "timestamp": "2023-02-01T07:00:00.000Z", + "timestamp": "2023-03-01T07:00:00.000Z", "serviceEstimates": [ { "cloudProvider": "AWS", @@ -289,7 +289,7 @@ ] }, { - "timestamp": "2023-01-01T07:00:00.000Z", + "timestamp": "2023-02-01T07:00:00.000Z", "serviceEstimates": [ { "cloudProvider": "AWS", @@ -364,7 +364,7 @@ ] }, { - "timestamp": "2022-12-01T07:00:00.000Z", + "timestamp": "2023-01-01T07:00:00.000Z", "serviceEstimates": [ { "cloudProvider": "AWS", @@ -439,7 +439,7 @@ ] }, { - "timestamp": "2022-11-01T06:00:00.000Z", + "timestamp": "2022-12-01T07:00:00.000Z", "serviceEstimates": [ { "cloudProvider": "AWS", @@ -514,7 +514,7 @@ ] }, { - "timestamp": "2022-10-01T06:00:00.000Z", + "timestamp": "2022-11-01T06:00:00.000Z", "serviceEstimates": [ { "cloudProvider": "AWS", @@ -589,7 +589,7 @@ ] }, { - "timestamp": "2022-09-01T06:00:00.000Z", + "timestamp": "2022-10-01T06:00:00.000Z", "serviceEstimates": [ { "cloudProvider": "AWS", @@ -664,7 +664,7 @@ ] }, { - "timestamp": "2022-08-01T06:00:00.000Z", + "timestamp": "2022-09-01T06:00:00.000Z", "serviceEstimates": [ { "cloudProvider": "AWS", @@ -739,7 +739,7 @@ ] }, { - "timestamp": "2022-07-01T06:00:00.000Z", + "timestamp": "2022-08-01T06:00:00.000Z", "serviceEstimates": [ { "cloudProvider": "AWS", @@ -814,7 +814,7 @@ ] }, { - "timestamp": "2022-06-01T06:00:00.000Z", + "timestamp": "2022-07-01T06:00:00.000Z", "serviceEstimates": [ { "cloudProvider": "AWS", @@ -889,7 +889,7 @@ ] }, { - "timestamp": "2022-05-01T06:00:00.000Z", + "timestamp": "2022-06-01T06:00:00.000Z", "serviceEstimates": [ { "cloudProvider": "AWS", @@ -964,7 +964,7 @@ ] }, { - "timestamp": "2022-04-01T06:00:00.000Z", + "timestamp": "2022-05-01T06:00:00.000Z", "serviceEstimates": [ { "cloudProvider": "AWS", @@ -1039,7 +1039,7 @@ ] }, { - "timestamp": "2022-03-01T07:00:00.000Z", + "timestamp": "2022-04-01T06:00:00.000Z", "serviceEstimates": [ { "cloudProvider": "AWS", @@ -1114,7 +1114,7 @@ ] }, { - "timestamp": "2022-02-01T07:00:00.000Z", + "timestamp": "2022-03-01T07:00:00.000Z", "serviceEstimates": [ { "cloudProvider": "AWS", From 93cf993f0ac13372052ccab1b4b73e893f29dbcb Mon Sep 17 00:00:00 2001 From: Cam Casher Date: Wed, 3 May 2023 13:49:14 -0600 Subject: [PATCH 062/251] updates mock data --- .changeset/tasty-bats-fail.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .changeset/tasty-bats-fail.md diff --git a/.changeset/tasty-bats-fail.md b/.changeset/tasty-bats-fail.md new file mode 100644 index 000000000..b06cd5c83 --- /dev/null +++ b/.changeset/tasty-bats-fail.md @@ -0,0 +1,7 @@ +--- +'@cloud-carbon-footprint/api': patch +'@cloud-carbon-footprint/client': patch +'@cloud-carbon-footprint/create-app': patch +--- + +updates mock data From d4643e64db3443732fa584afe2c720f050b91604 Mon Sep 17 00:00:00 2001 From: Cam Casher Date: Wed, 3 May 2023 13:51:08 -0600 Subject: [PATCH 063/251] undo's demo app yml change --- .github/workflows/demo-app.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/demo-app.yml b/.github/workflows/demo-app.yml index 3afdf2dfe..28500e600 100644 --- a/.github/workflows/demo-app.yml +++ b/.github/workflows/demo-app.yml @@ -13,7 +13,7 @@ concurrency: cancel-in-progress: true jobs: deploy-demo-app: -# if: github.event.pull_request.head.ref == 'changeset-release/trunk' + if: github.event.pull_request.head.ref == 'changeset-release/trunk' runs-on: ubuntu-latest container: image: node:16.19-alpine3.17 From 78029752c244d4e62b2a14e415be5860ae1b4a21 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 3 May 2023 19:53:51 +0000 Subject: [PATCH 064/251] Version Packages --- .changeset/tasty-bats-fail.md | 7 ------- packages/api/CHANGELOG.md | 6 ++++++ packages/api/package.json | 2 +- packages/client/CHANGELOG.md | 6 ++++++ packages/client/package.json | 2 +- packages/create-app/CHANGELOG.md | 6 ++++++ packages/create-app/package.json | 2 +- 7 files changed, 21 insertions(+), 10 deletions(-) delete mode 100644 .changeset/tasty-bats-fail.md diff --git a/.changeset/tasty-bats-fail.md b/.changeset/tasty-bats-fail.md deleted file mode 100644 index b06cd5c83..000000000 --- a/.changeset/tasty-bats-fail.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -'@cloud-carbon-footprint/api': patch -'@cloud-carbon-footprint/client': patch -'@cloud-carbon-footprint/create-app': patch ---- - -updates mock data diff --git a/packages/api/CHANGELOG.md b/packages/api/CHANGELOG.md index d6b972604..f2383a98d 100644 --- a/packages/api/CHANGELOG.md +++ b/packages/api/CHANGELOG.md @@ -1,5 +1,11 @@ # @cloud-carbon-footprint/api +## 1.6.2 + +### Patch Changes + +- 93cf993f: updates mock data + ## 1.6.1 ### Patch Changes diff --git a/packages/api/package.json b/packages/api/package.json index 9333bfead..f9d0453b0 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -1,6 +1,6 @@ { "name": "@cloud-carbon-footprint/api", - "version": "1.6.1", + "version": "1.6.2", "license": "Apache-2.0", "description": "The API endpoint as an entrypoint to get cloud energy and carbon emissions. Optionally used by the client dashboard.", "main": "src/server.ts", diff --git a/packages/client/CHANGELOG.md b/packages/client/CHANGELOG.md index 4380c6c65..9d98bfd81 100644 --- a/packages/client/CHANGELOG.md +++ b/packages/client/CHANGELOG.md @@ -1,5 +1,11 @@ # @cloud-carbon-footprint/client +## 4.1.2 + +### Patch Changes + +- 93cf993f: updates mock data + ## 4.1.1 ### Patch Changes diff --git a/packages/client/package.json b/packages/client/package.json index c44cde9ed..2004b7e95 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -1,6 +1,6 @@ { "name": "@cloud-carbon-footprint/client", - "version": "4.1.1", + "version": "4.1.2", "license": "Apache-2.0", "description": "The front-end dashboard for Cloud Carbon Footprint.", "main": "src/index.tsx", diff --git a/packages/create-app/CHANGELOG.md b/packages/create-app/CHANGELOG.md index 51a674eb8..2bc227c34 100644 --- a/packages/create-app/CHANGELOG.md +++ b/packages/create-app/CHANGELOG.md @@ -1,5 +1,11 @@ # @cloud-carbon-footprint/create-app +## 2.3.5 + +### Patch Changes + +- 93cf993f: updates mock data + ## 2.3.4 ### Patch Changes diff --git a/packages/create-app/package.json b/packages/create-app/package.json index 325816756..4c1bb2932 100644 --- a/packages/create-app/package.json +++ b/packages/create-app/package.json @@ -1,6 +1,6 @@ { "name": "@cloud-carbon-footprint/create-app", - "version": "2.3.4", + "version": "2.3.5", "license": "Apache-2.0", "description": "Create app package for Cloud Carbon Footprint", "main": "dist/index.js", From ae2836df14e9992061248415e64c0a6eb0ee0255 Mon Sep 17 00:00:00 2001 From: Cam Casher Date: Wed, 3 May 2023 15:05:07 -0600 Subject: [PATCH 065/251] updates mock data scripts for demo app --- .github/workflows/demo-app.yml | 9 ++++- scripts/create-mock-data.js | 40 +++++++++++++++++++ ...lient-mock-data.js => update-mock-data.js} | 7 +++- 3 files changed, 52 insertions(+), 4 deletions(-) create mode 100644 scripts/create-mock-data.js rename scripts/{create-client-mock-data.js => update-mock-data.js} (96%) diff --git a/.github/workflows/demo-app.yml b/.github/workflows/demo-app.yml index 28500e600..89dc799a4 100644 --- a/.github/workflows/demo-app.yml +++ b/.github/workflows/demo-app.yml @@ -8,12 +8,17 @@ on: paths-ignore: - 'microsite/**' - '.github/workflows/deploy-microsite.yml' + push: + branches: [ trunk ] + paths-ignore: + - 'microsite/**' + - '.github/workflows/deploy-microsite.yml' concurrency: group: ${{ github.workflow }} cancel-in-progress: true jobs: deploy-demo-app: - if: github.event.pull_request.head.ref == 'changeset-release/trunk' +# if: github.event.pull_request.head.ref == 'changeset-release/trunk' runs-on: ubuntu-latest container: image: node:16.19-alpine3.17 @@ -70,7 +75,7 @@ jobs: mkdir packages/client/stub-server/api mkdir packages/client/stub-server/api/regions - node scripts/create-client-mock-data.js + node scripts/create-mock-data.js # Copy static build files to demo Google Cloud Bucket gsutil cp -r packages/client/build/* gs://${GOOGLE_DEMO_STORAGE_BUCKET} diff --git a/scripts/create-mock-data.js b/scripts/create-mock-data.js new file mode 100644 index 000000000..809b11e17 --- /dev/null +++ b/scripts/create-mock-data.js @@ -0,0 +1,40 @@ +/* + * © 2021 Thoughtworks, Inc. + */ + +const fs = require('fs') +const path = require('path') +const { update } = require('./update-mock-data') + +async function main() { + await update() + const data = fs.readFileSync( + path.resolve(__dirname, `../packages/client/stub-server/mockData.json`), + 'utf8', + ) + + const mockData = JSON.parse(data) + + Object.keys(mockData).forEach((key) => { + const pathSuffix = key === 'emissions' ? `regions/${key}` : key + + fs.writeFile( + path.resolve( + __dirname, + `../packages/client/stub-server/api/${pathSuffix}`, + ), + JSON.stringify(mockData[key]), + (err) => { + if (err) { + console.error(err) + return + } + }, + ) + }) +} + +main().catch((error) => { + console.error(error.stack) + process.exit(1) +}) diff --git a/scripts/create-client-mock-data.js b/scripts/update-mock-data.js similarity index 96% rename from scripts/create-client-mock-data.js rename to scripts/update-mock-data.js index 08813ae3e..f65d0c416 100644 --- a/scripts/create-client-mock-data.js +++ b/scripts/update-mock-data.js @@ -6,7 +6,8 @@ const fs = require('fs') const path = require('path') const moment = require('moment') -async function main() { +async function update() { + console.log('hit update') const data = fs.readFileSync( path.resolve(__dirname, `../packages/client/stub-server/mockData.json`), 'utf8', @@ -98,7 +99,9 @@ const writeToFile = async ( } -main().catch((error) => { +update().catch((error) => { console.error(error.stack) process.exit(1) }) + +module.exports = { update } \ No newline at end of file From c2fc8a0c443c7c3d109889e515c6b4cd981b5cd1 Mon Sep 17 00:00:00 2001 From: 4upz Date: Wed, 3 May 2023 17:27:01 -0400 Subject: [PATCH 066/251] removes unused google-cloud depedency from app package --- packages/app/package.json | 1 - yarn.lock | 1 - 2 files changed, 2 deletions(-) diff --git a/packages/app/package.json b/packages/app/package.json index aa4fcd2c6..091fce5cf 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -45,7 +45,6 @@ "@cloud-carbon-footprint/common": "^1.10.0", "@cloud-carbon-footprint/gcp": "^0.12.0", "@cloud-carbon-footprint/on-premise": "^0.1.1", - "@google-cloud/monitoring": "^2.3.5", "@google-cloud/storage": "^5.16.1", "@sovpro/delimited-stream": "^1.1.0", "moment": "^2.29.1", diff --git a/yarn.lock b/yarn.lock index e4c4d7e53..9a9aed373 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2251,7 +2251,6 @@ __metadata: "@cloud-carbon-footprint/common": ^1.10.0 "@cloud-carbon-footprint/gcp": ^0.12.0 "@cloud-carbon-footprint/on-premise": ^0.1.1 - "@google-cloud/monitoring": ^2.3.5 "@google-cloud/storage": ^5.16.1 "@sovpro/delimited-stream": ^1.1.0 "@types/jest": ^27.4.0 From 116341873ced5e28678535eb5f7e811d1e6e8d9a Mon Sep 17 00:00:00 2001 From: Cam Casher Date: Wed, 3 May 2023 15:27:34 -0600 Subject: [PATCH 067/251] remove script console log --- scripts/update-mock-data.js | 1 - 1 file changed, 1 deletion(-) diff --git a/scripts/update-mock-data.js b/scripts/update-mock-data.js index f65d0c416..e9dbcabe1 100644 --- a/scripts/update-mock-data.js +++ b/scripts/update-mock-data.js @@ -7,7 +7,6 @@ const path = require('path') const moment = require('moment') async function update() { - console.log('hit update') const data = fs.readFileSync( path.resolve(__dirname, `../packages/client/stub-server/mockData.json`), 'utf8', From f1b306570692ba6a9024ab71e9bc3ed0edfc8ae1 Mon Sep 17 00:00:00 2001 From: Cam Casher Date: Wed, 3 May 2023 15:36:14 -0600 Subject: [PATCH 068/251] reverts demo app yml changes --- .github/workflows/demo-app.yml | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/.github/workflows/demo-app.yml b/.github/workflows/demo-app.yml index 89dc799a4..7018545f4 100644 --- a/.github/workflows/demo-app.yml +++ b/.github/workflows/demo-app.yml @@ -8,17 +8,12 @@ on: paths-ignore: - 'microsite/**' - '.github/workflows/deploy-microsite.yml' - push: - branches: [ trunk ] - paths-ignore: - - 'microsite/**' - - '.github/workflows/deploy-microsite.yml' concurrency: group: ${{ github.workflow }} cancel-in-progress: true jobs: deploy-demo-app: -# if: github.event.pull_request.head.ref == 'changeset-release/trunk' + if: github.event.pull_request.head.ref == 'changeset-release/trunk' runs-on: ubuntu-latest container: image: node:16.19-alpine3.17 From 804eb1ca17d6f4cba9f8c9cab78fb6e4aec81834 Mon Sep 17 00:00:00 2001 From: Arik Smith Date: Fri, 5 May 2023 16:02:04 +0000 Subject: [PATCH 069/251] [#1114] adds documentation for the request splitting and additional performance considerations --- .../DataPersistenceAndCaching.md | 39 +++++++++++++------ .../PerformanceConsiderations.md | 33 ++++++++++++++-- 2 files changed, 57 insertions(+), 15 deletions(-) diff --git a/microsite/docs/ConfigurationOptions/DataPersistenceAndCaching.md b/microsite/docs/ConfigurationOptions/DataPersistenceAndCaching.md index 70ff6a340..e8ed7bf2a 100644 --- a/microsite/docs/ConfigurationOptions/DataPersistenceAndCaching.md +++ b/microsite/docs/ConfigurationOptions/DataPersistenceAndCaching.md @@ -13,16 +13,16 @@ We offer the following data caching methods of varying levels of simplicity and - Google Cloud Storage - MongoDB (local or cloud database) -### JSON File +## JSON File -#### Local Filesystem +### Local Filesystem To make local development a pleasant experience with a quick feedback loop, we have a no-setup local caching method that uses a JSON file that is automatically generated. This file will be created in either the packages/api or packages/cli directory – typically with the name of the grouping method included (i.e. `estimates.month.cache.json`. If you would like to see up-to-date estimates, you will have to delete `packages/cli/estimates.[grouping].cache.json` and/or `packages/api/estimates.[grouping].cache.json`. Depending on how much usage you have, it could take several minutes to fetch up-to-date estimates and regenerate the cache file. Note: If you don’t see one of these files, don’t worry. Simply start the server, and load the app for the first time. Currently, this caching method is planned to be deprecated and will receive decreased support for incoming features made to the app. This is due to its poor scalability which creates issues for those with very large usage data on a typical enterprise or organizational scale. However, it is a great way to get up and start with the app quickly and still appropriate for small-scale users. -#### Google Cloud Storage +### Google Cloud Storage As an expansion to the local cache method, you can use the same JSON file generation within a Google Cloud Storage bucket. If you are experiencing long load times in your staging or production environments, or simply wish to forego the local filesystem, the option to store a JSON cache file in the cloud may be the best option for you. This could be especially helpful in improving re-estimate speeds for your deployed environments. In order to use the Google Cloud option, you have to set the following variables in your `packages/api/.env` or `packages/cli/.env` file: @@ -40,7 +40,7 @@ For JSON Filesystem cache modes, CCF uses read and write streams to get and set For this reason, we have chosen to focus our support on the MongoDB cache mode, where we have implemented pagination for the REST API response which will scale better and avoid the memory limitations. We believe the JSON File cache mode is a good choice for smaller data sets. -### MongoDB Storage +## MongoDB Storage Users or organizations with large amounts of usage data may have difficulty using the default local caching method. For those wishing to persist a larger scale of data, we offer the option of configuring a MongoDB instance to store your estimates. Similar to the local cache system, this method will also speed up subsequent calls to the API and is the recommended and fully supported caching method. @@ -64,7 +64,7 @@ MONGODB_CREDENTIALS=/path-to-credentials.pem After calculating estimates for the first time, the app will create a new collection titled “ccf”. Estimates will be separated into timestamps and stored into a collection that is named according to the current grouping method (i.e. “estimates-by-month). -#### Paginating Estimates +### Paginating Estimates Since the MongoDB storage method is capable of storing a large amount of estimates, it is possible the estimates for a requested date range will exceed the ideal size of a REST API response. To accommodate this, we have enabled pagination when fetching estimates using this method alongside the CCF client or querying the API directly. @@ -87,7 +87,7 @@ _Note: This feature only works when the MONGODB cache mode is enabled. The limit _To avoid potential issues with memory limitations when loading and aggregating estimates from the cache, a default value of 50,000 documents will be used for the page limit. This ensures that individual timestamps with significantly large amount of estimates are handled properly. Custom limit values that exceed this number will result in a validation error._ -#### Filtering Estimates +### Filtering Estimates With the MongoDB cache mode, CCF is now supporting the capability to filter estimates by the following keys and request parameters: @@ -116,9 +116,9 @@ _Note: Filtering for Mongodb will only work to filter existing cached data in th _Filtering is only supported via the API and is not yet supported on the client._ -### Caching Configurations +## Caching Configurations -#### Ensure real-time estimates +### Ensure Real-Time Estimates In order to make local development a pleasant experience with a quick feedback loop, we have a cache file that is automatically generated. If you would like to see up-to-date estimates, you will have to delete `packages/cli/estimates.cache.json` and/or `packages/api/estimates.cache.json`. If you are using MongoDB or GCS to store estimates, you will either have to delete the collection or the estimates file in the storage bucket respectively. Depending on how much usage you have, it could take several minutes to fetch up-to-date estimates and regenerate the cache file. @@ -130,16 +130,31 @@ Or when using the client, you can set an optional environment variable: `DISABLE_CACHE=true` -#### Seeding cache file +## Seeding Cache File -We have an option to run the server side API calls as a background job. This can be useful for larger amounts of data to query from the cloud providers and will have no timeout limit when running with the browser. Before running the script, you will need to set the necessary configurations in a `.env` file in the CLI directory. +We have an option to run the server-side API calls as a background job. This can be useful for larger amounts of data to query from the cloud providers and will have no timeout limit when running with the browser. Before running the script, you will need to set the necessary configurations in a `.env` file in the CLI directory. From the root directory, run: `yarn seed-cache-file` -You will then be prompted enter a start date, end date and groupBy parameter. Optionally, you can specify a specific cloud provider to seed. This will allow you to append estimations to given dates in your requested time frame that may be missing from a newly configured cloud provider (currently only supported with a MongoDB caching mode). +You will then be prompted to enter a start date, end date and groupBy parameter. -Once this process is finished running. A new cache file will be created in the CLI directory. In order to use the cache file to run with the front-end client package, you will have to copy the cache file to the API directory before starting the application. +Optionally, you can specify a specific cloud provider to seed. This will allow you to append estimations to given dates in your requested time frame that may be missing from a newly configured cloud provider (currently only supported with a MongoDB caching mode). + +Once this process is finished running, estimates will either be saved to a newly created JSON file in the `packages/cli` directory or into your configured MongoDB instance. If using the JSON method, in order to use the cache file to run with the front-end client package, you will have to copy the cache file to the API directory before starting the application. _Note: If you end up seeing an error due to memory limitations, you will either have to adjust to a smaller date range or change the grouping method._ + +### Fetch Methods + +When starting the cache file seeding, one of the last prompts will ask you to specify a fetch method for the requests that the script will make. This allows for greater flexibility with handling date ranges with potentially large data that will be too large to calculate in a single API request. You will be presented with the following options: + +- **Single** - Seed the cache file using a _single request_ to the API (Default) +- **Split** - Seed the cache file using multiple requests split according to specified number of days + +By default, the script will fetch estimates using a single request. + +If you choose to split your requests, you will be asked an optional follow-up question of how many days to fetch per request. This will allow it to fetch the provided number of days or to fetch a default of one day per request until the date range is covered. + +We recommend using this method when dealing with a large amount of usage data within a date range that may cause issues when running your app, especially for historical data, as described in the [Performance Considerations](PerformanceConsiderations.md) section. diff --git a/microsite/docs/ConfigurationOptions/PerformanceConsiderations.md b/microsite/docs/ConfigurationOptions/PerformanceConsiderations.md index ef38d551d..2eaf29143 100644 --- a/microsite/docs/ConfigurationOptions/PerformanceConsiderations.md +++ b/microsite/docs/ConfigurationOptions/PerformanceConsiderations.md @@ -5,17 +5,44 @@ slug: /performance-considerations sidebar_position: 2 --- -## Options to Improve Query Performance +## Improving Query Performance and Handling Large Data -When running very large amounts of data with the default configuration of querying each day for the previous year, we have noticed that the time it takes to start the app increases significantly. We have added optional configuration to help with this performance issue to query and date filter in a few different ways: +When running very large amounts of data, we noticed there may be some issues in the time it takes to load estimates and to receive a response from the app. Depending on the configuration of your environment, querying for dates with significant amount of usage may result in long request times, runtime errors, or even cause requests to fail. While we work hard to continuously optimize and improve CCF for large organizations and usages, we recommend considering the following to help avoid some of these issues: -### Date Range +## Considerations for Large Requests + +When querying for a specific date range, you may encounter instances where the requested date range has a large amount of usage for the app to process. Depending on the environment in which your CCF app is running, this may cause requests to stall or timeout due to memory issues. We recommend doing the following: + +- Backfill and seed large amounts of data using the [Seed Cache File](DataPersistenceAndCaching.md#seeding-cache-file) method. (Recommended) +- Decrease the date range of the request to reduce the amount of estimates needed to be calculated +- Disable other cloud provider configurations to reduce the amount of cloud providers being queried in one request + +Backfilling and seeding the cache is useful for handling the calculation of a large amount of data. Using this method will allow for calculated estimates to be saved, enabling faster subsequent requests with less overhead for those date ranges to be viewed in the client or output in the API/CLI. + +## Date Range Considerations + +### Date Range via the Client + +When using the client dashboard and running very large amounts of data with the default configuration of querying each day for the previous year, we have noticed that the time it takes to start the app increases significantly. In your `packages/client/.env` file, you can provide the following variables for a custom date range: +#### Custom Date Range + +Optionally set the date range to query data based on custom start/end timestamps. + +- `REACT_APP_START_DATE` (example value: 1-01-2023) +- `REACT_APP_END_DATE` (example value: 3-01-2023) + +#### Date Range from Today (Legacy Configuration) + +Optionally set the date range to query the data starting back in days/weeks/months/quarters/years to the current date + - `REACT_APP_DATE_RANGE_TYPE` (example values: day(s), week(s), month(s), etc..) - `REACT_APP_DATE_RANGE_VALUE` (example values: number correlating to day/week/month etc..) +_Note_: If set, these will take least precedence over all other date range configurations. + ### Group By Timestamp in Queries In your `packages/client/.env` file, you can provide the following variable for a custom query option to group the data by date type: From 9bc3ad324e448e6a95a70b8a55d66ff75ef27e88 Mon Sep 17 00:00:00 2001 From: Arik Smith Date: Tue, 9 May 2023 16:55:38 -0400 Subject: [PATCH 070/251] adds date format to documentation --- .../docs/ConfigurationOptions/PerformanceConsiderations.md | 4 ++-- packages/client/.env.template | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/microsite/docs/ConfigurationOptions/PerformanceConsiderations.md b/microsite/docs/ConfigurationOptions/PerformanceConsiderations.md index 2eaf29143..cf69fc263 100644 --- a/microsite/docs/ConfigurationOptions/PerformanceConsiderations.md +++ b/microsite/docs/ConfigurationOptions/PerformanceConsiderations.md @@ -31,8 +31,8 @@ In your `packages/client/.env` file, you can provide the following variables for Optionally set the date range to query data based on custom start/end timestamps. -- `REACT_APP_START_DATE` (example value: 1-01-2023) -- `REACT_APP_END_DATE` (example value: 3-01-2023) +- `REACT_APP_START_DATE` (yyyy-mm-dd) +- `REACT_APP_END_DATE` (yyyy-mm-dd) #### Date Range from Today (Legacy Configuration) diff --git a/packages/client/.env.template b/packages/client/.env.template index a92772142..c418ea020 100644 --- a/packages/client/.env.template +++ b/packages/client/.env.template @@ -6,6 +6,7 @@ REACT_APP_PREVIOUS_YEAR_OF_USAGE=true # Modern date range config - Optionally set the date range to query data based on custom start/end timestamps # If set, these will take precedence over configured date range value/type, but not previous year of usage +# yyyy-mm-dd REACT_APP_START_DATE=01-01-2022 REACT_APP_END_DATE=06-01-2022 From 47ddb3c5560979fdacfa1e7bc172098704abc4b9 Mon Sep 17 00:00:00 2001 From: Georgia Pantelidou <82615471+gpantelidou@users.noreply.github.com> Date: Mon, 15 May 2023 10:17:26 +0200 Subject: [PATCH 071/251] Add Climatiq to list of adopters --- ADOPTERS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/ADOPTERS.md b/ADOPTERS.md index df2a3f407..764525cf2 100644 --- a/ADOPTERS.md +++ b/ADOPTERS.md @@ -2,4 +2,5 @@ |----------------------------------------------| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | [Thoughtworks](https://www.thoughtworks.com) | green-cloud@thoughtworks.com | Measure and reduce cloud carbon emissions for Thoughtworks. Deployed internally on GCP. Expanding feature set and refining methodology. | | [OSP](https://www.osp.de/en) | ccf@osp.de | Measure and reduce cloud carbon emissions for OSP. The tool is part of the tool infrastructure of the OSP Sustainable Programming Initiative (SPI). The tool is part of a strategy to become more energy efficient with our cloud projects. | +|[Climatiq](https://www.climatiq.io/) | hello@climatiq.io | Measure the embodied and use-phase cloud emissions of GCP, AZURE, and AWS. | | | | | From fcebb6397b8915a949aa70ff035f9ae087599fea Mon Sep 17 00:00:00 2001 From: David H Date: Fri, 12 May 2023 15:20:57 +0200 Subject: [PATCH 072/251] Update AzureRegions.ts Add missing regions based on my logs: [AzureRegions] warn: Found unknown azure region 'Europe', please add it to the AzureRegions.ts file and submit a PR, thank you! [AzureRegions] warn: Found unknown azure region 'EuropeWest', please add it to the AzureRegions.ts file and submit a PR, thank you! [AzureRegions] warn: Found unknown azure region 'qatarcentral', please add it to the AzureRegions.ts file and submit a PR, thank you! [AzureRegions] warn: Found unknown azure region 'uaen', please add it to the AzureRegions.ts file and submit a PR, thank you! --- packages/azure/src/lib/AzureRegions.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/azure/src/lib/AzureRegions.ts b/packages/azure/src/lib/AzureRegions.ts index 8f24c09c6..1ddc92547 100644 --- a/packages/azure/src/lib/AzureRegions.ts +++ b/packages/azure/src/lib/AzureRegions.ts @@ -71,13 +71,14 @@ export const AZURE_REGIONS = { IN_WEST: { name: 'westindia', options: ['westindia'] }, // EU regions + EU: { name: 'europe', options: ['Europe'] }, EU_NORTH: { name: 'northeurope', options: ['northeurope', 'europenorth', 'NORTHEUROPE', 'EU North'], }, EU_WEST: { name: 'westeurope', - options: ['westeurope', 'WESTEUROPE', 'EU West'], + options: ['westeurope', 'WESTEUROPE', 'EU West', 'EuropeWest'], }, EU_FRANCE_CENTRAL: { name: 'francecentral', options: ['francecentral'] }, EU_FRANCE_SOUTH: { name: 'francesouth', options: ['francesouth'] }, @@ -108,7 +109,8 @@ export const AZURE_REGIONS = { // Middle East regions ME_UAE: { name: 'uae', options: ['uae'] }, ME_UAE_CENTRAL: { name: 'uaecentral', options: ['uaecentral'] }, - ME_UAE_NORTH: { name: 'uaenorth', options: ['uaenorth'] }, + ME_UAE_NORTH: { name: 'uaenorth', options: ['uaenorth', 'uaen'] }, + ME_QATAR_CENTRAL: { name: 'qatarcentral', options: ['qatarcentral'] }, // America regions US_CANADA: { name: 'canada', options: ['canada'] }, From 2eba59bce9617a24a218c816399153a639ae7c03 Mon Sep 17 00:00:00 2001 From: David H Date: Wed, 17 May 2023 15:01:04 +0200 Subject: [PATCH 073/251] Update AzureRegions.ts by removing EU region --- packages/azure/src/lib/AzureRegions.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/azure/src/lib/AzureRegions.ts b/packages/azure/src/lib/AzureRegions.ts index 1ddc92547..a91f105f4 100644 --- a/packages/azure/src/lib/AzureRegions.ts +++ b/packages/azure/src/lib/AzureRegions.ts @@ -71,7 +71,6 @@ export const AZURE_REGIONS = { IN_WEST: { name: 'westindia', options: ['westindia'] }, // EU regions - EU: { name: 'europe', options: ['Europe'] }, EU_NORTH: { name: 'northeurope', options: ['northeurope', 'europenorth', 'NORTHEUROPE', 'EU North'], From 7fd0e620490f1502ea1a5f059b91eb9b62d44d26 Mon Sep 17 00:00:00 2001 From: Arik Smith Date: Thu, 18 May 2023 17:53:09 -0400 Subject: [PATCH 074/251] updates gcp emissions factors and adds new regions --- .../src/__tests__/BillingExportTable.test.ts | 140 +++++++++--------- .../gcp/src/__tests__/Recommendations.test.ts | 26 ++-- .../domain/GcpFootprintEstimationConstants.ts | 130 ++++++++-------- packages/gcp/src/lib/GCPRegions.ts | 7 + 4 files changed, 162 insertions(+), 141 deletions(-) diff --git a/packages/gcp/src/__tests__/BillingExportTable.test.ts b/packages/gcp/src/__tests__/BillingExportTable.test.ts index 594c85656..8ea3ab318 100644 --- a/packages/gcp/src/__tests__/BillingExportTable.test.ts +++ b/packages/gcp/src/__tests__/BillingExportTable.test.ts @@ -102,7 +102,7 @@ describe('GCP BillingExportTable Service', () => { serviceEstimates: [ { kilowattHours: 0.005190060141275502, - co2e: 0.0000024912288678122412, + co2e: 0.0000022524861013135677, usesAverageCPUConstant: false, cloudProvider: 'GCP', accountId: accountId, @@ -153,7 +153,7 @@ describe('GCP BillingExportTable Service', () => { serviceEstimates: [ { kilowattHours: 1.4232136891223488, - co2e: 0.0006831425707787274, + co2e: 0.0006176747410790994, usesAverageCPUConstant: false, cloudProvider: 'GCP', accountId: accountId, @@ -165,7 +165,7 @@ describe('GCP BillingExportTable Service', () => { }, { kilowattHours: 0.05865088888888889, - co2e: 0.00002815242666666667, + co2e: 0.00002545448577777778, usesAverageCPUConstant: true, cloudProvider: 'GCP', accountId: accountId, @@ -185,7 +185,7 @@ describe('GCP BillingExportTable Service', () => { serviceEstimates: [ { kilowattHours: 0.1402554516971577, - co2e: 0.0000109399252323783, + co2e: 0.000008415327101829462, usesAverageCPUConstant: false, cloudProvider: 'GCP', accountId: accountId, @@ -250,7 +250,7 @@ describe('GCP BillingExportTable Service', () => { accountId: accountId, accountName: accountName, cloudProvider: 'GCP', - co2e: 1.9419763604819328e-8, + co2e: 1.9505313224223818e-8, cost: 120, kilowattHours: 0.000042774809702245215, tags: {}, @@ -319,7 +319,7 @@ describe('GCP BillingExportTable Service', () => { serviceEstimates: [ { kilowattHours: 0.00028446168676757815, - co2e: 1.3654160964843751e-7, + co2e: 1.2345637205712892e-7, usesAverageCPUConstant: false, cloudProvider: 'GCP', accountId: accountId, @@ -331,7 +331,7 @@ describe('GCP BillingExportTable Service', () => { }, { kilowattHours: 8.234108336182544e-11, - co2e: 4.5616960182451294e-14, + co2e: 3.8206262679887003e-14, usesAverageCPUConstant: false, cloudProvider: 'GCP', accountId: accountId, @@ -355,7 +355,7 @@ describe('GCP BillingExportTable Service', () => { }, { kilowattHours: 4.4601420154322116e-11, - co2e: 3.2157623931266244e-14, + co2e: 2.9882951503395816e-14, usesAverageCPUConstant: false, cloudProvider: 'GCP', accountId: accountId, @@ -367,7 +367,7 @@ describe('GCP BillingExportTable Service', () => { }, { kilowattHours: 0.034632, - co2e: 0.000015722928, + co2e: 0.000015792192000000003, usesAverageCPUConstant: false, cloudProvider: 'GCP', accountId: accountId, @@ -379,7 +379,7 @@ describe('GCP BillingExportTable Service', () => { }, { kilowattHours: 0.00023740234375, - co2e: 8.570224609375e-8, + co2e: 7.335732421875e-8, usesAverageCPUConstant: false, cloudProvider: 'GCP', accountId: accountId, @@ -391,7 +391,7 @@ describe('GCP BillingExportTable Service', () => { }, { kilowattHours: 6.587286728972686e-10, - co2e: 4.749433731589306e-13, + co2e: 4.4134821084116997e-13, usesAverageCPUConstant: false, cloudProvider: 'GCP', accountId: accountId, @@ -442,7 +442,7 @@ describe('GCP BillingExportTable Service', () => { serviceEstimates: [ { kilowattHours: 0.00001691015625, - co2e: 7.6772109375e-9, + co2e: 7.711031250000001e-9, usesAverageCPUConstant: false, cloudProvider: 'GCP', accountId: accountId, @@ -454,7 +454,7 @@ describe('GCP BillingExportTable Service', () => { }, { kilowattHours: 0.00003412500000000001, - co2e: 1.5492750000000005e-8, + co2e: 1.5561000000000006e-8, usesAverageCPUConstant: false, cloudProvider: 'GCP', accountId: accountId, @@ -504,8 +504,8 @@ describe('GCP BillingExportTable Service', () => { timestamp: new Date('2020-11-02'), serviceEstimates: [ { - kilowattHours: 17.590322771549975, - co2e: 0.008443354930343988, + kilowattHours: 18.09392953361943, + co2e: 0.007852765417590833, usesAverageCPUConstant: true, cloudProvider: 'GCP', accountId: accountId, @@ -516,8 +516,8 @@ describe('GCP BillingExportTable Service', () => { region: 'us-east1', }, { - kilowattHours: 0.211878579423001, - co2e: 0.000016526529194994077, + kilowattHours: 0.2544911532499013, + co2e: 0.000015269469194994078, usesAverageCPUConstant: true, cloudProvider: 'GCP', accountId: accountId, @@ -536,8 +536,8 @@ describe('GCP BillingExportTable Service', () => { timestamp: new Date('2020-10-28'), serviceEstimates: [ { - kilowattHours: 12.634439456249067, - co2e: 0.006064530938999552, + kilowattHours: 13.255962888254281, + co2e: 0.005753087893502358, usesAverageCPUConstant: true, cloudProvider: 'GCP', accountId: accountId, @@ -587,8 +587,8 @@ describe('GCP BillingExportTable Service', () => { timestamp: new Date('2020-11-02'), serviceEstimates: [ { - kilowattHours: 0.10281277077223262, - co2e: 0.00004667699793059361, + kilowattHours: 0.10269717440919651, + co2e: 0.00004682991153059361, usesAverageCPUConstant: true, cloudProvider: 'GCP', accountId: accountId, @@ -607,8 +607,8 @@ describe('GCP BillingExportTable Service', () => { timestamp: new Date('2020-10-28'), serviceEstimates: [ { - kilowattHours: 0.36541815348500395, - co2e: 0.00016589984168219178, + kilowattHours: 0.36465495281182403, + co2e: 0.00016628265848219178, usesAverageCPUConstant: true, cloudProvider: 'GCP', accountId: accountId, @@ -627,8 +627,8 @@ describe('GCP BillingExportTable Service', () => { timestamp: new Date('2020-11-03'), serviceEstimates: [ { - kilowattHours: 0.33462656235264876, - co2e: 0.00015192045930810253, + kilowattHours: 0.33423618476684713, + co2e: 0.00015241170025368233, usesAverageCPUConstant: true, cloudProvider: 'GCP', accountId: accountId, @@ -647,8 +647,8 @@ describe('GCP BillingExportTable Service', () => { timestamp: new Date('2020-11-04'), serviceEstimates: [ { - kilowattHours: 0.08549324069338539, - co2e: 0.00006164062653993087, + kilowattHours: 0.0867815253369615, + co2e: 0.0000581436219757642, usesAverageCPUConstant: true, cloudProvider: 'GCP', accountId: accountId, @@ -668,7 +668,7 @@ describe('GCP BillingExportTable Service', () => { serviceEstimates: [ { kilowattHours: 0.06856871694444444, - co2e: 0.00004943804491694444, + co2e: 0.00004594104035277778, usesAverageCPUConstant: true, cloudProvider: 'GCP', accountId: accountId, @@ -747,7 +747,7 @@ describe('GCP BillingExportTable Service', () => { serviceEstimates: [ { kilowattHours: 89.32470187303058, - co2e: 0.042875856899054675, + co2e: 0.03876692061289526, usesAverageCPUConstant: true, cloudProvider: 'GCP', accountId: accountId, @@ -761,7 +761,7 @@ describe('GCP BillingExportTable Service', () => { accountId: 'test-account-id', accountName: 'test-account-name', cloudProvider: 'GCP', - co2e: 0.0010415086271501168, + co2e: 0.0008011604824231668, cost: 20, kilowattHours: 13.352674707052781, tags: {}, @@ -810,7 +810,7 @@ describe('GCP BillingExportTable Service', () => { accountId: accountId, accountName: accountName, cloudProvider: 'GCP', - co2e: 0.0004018621663586667, + co2e: 0.00036335037541596114, cost: 190, tags: {}, region: 'us-east1', @@ -822,7 +822,7 @@ describe('GCP BillingExportTable Service', () => { accountId: accountId, accountName: accountName, cloudProvider: 'GCP', - co2e: 0.0000021042472983585367, + co2e: 0.000001621253169531934, cost: 5, tags: {}, region: 'Unknown', @@ -834,7 +834,7 @@ describe('GCP BillingExportTable Service', () => { accountId: 'test-account-id', accountName: 'test-account-name', cloudProvider: 'GCP', - co2e: 0.000005120574505333334, + co2e: 0.000004629852781905555, cost: 10, kilowattHours: 0.010667863552777778, tags: {}, @@ -883,7 +883,7 @@ describe('GCP BillingExportTable Service', () => { accountId: accountId, accountName: accountName, cloudProvider: 'GCP', - co2e: 7.6772109375e-9, + co2e: 7.711031250000001e-9, cost: 170, kilowattHours: 0.00001691015625, tags: {}, @@ -895,9 +895,9 @@ describe('GCP BillingExportTable Service', () => { accountId: accountId, accountName: accountName, cloudProvider: 'GCP', - co2e: 2.19789390425157, + co2e: 2.0518022989814515, cost: 150, - kilowattHours: 3048.3965384903886, + kilowattHours: 3062.391491017092, tags: {}, region: 'asia-south1', serviceName: 'Kubernetes Engine', @@ -907,9 +907,9 @@ describe('GCP BillingExportTable Service', () => { accountId: accountId, accountName: accountName, cloudProvider: 'GCP', - co2e: 0.12238588237226934, + co2e: 0.12287556385109111, cost: 350, - kilowattHours: 269.5724281327519, + kilowattHours: 269.46395581379625, tags: {}, region: 'us-central1', serviceName: 'Kubernetes Engine', @@ -919,7 +919,7 @@ describe('GCP BillingExportTable Service', () => { accountId: accountId, accountName: accountName, cloudProvider: 'GCP', - co2e: 0.2087717970211498, + co2e: 0.1940043051375456, cost: 50, tags: {}, region: 'asia-south1', @@ -931,7 +931,7 @@ describe('GCP BillingExportTable Service', () => { accountId: accountId, accountName: accountName, cloudProvider: 'GCP', - co2e: 4.238710334472657e-7, + co2e: 3.579355393554688e-7, cost: 150, tags: {}, region: 'asia-east1', @@ -990,7 +990,7 @@ describe('GCP BillingExportTable Service', () => { accountId: accountId, accountName: accountName, cloudProvider: 'GCP', - co2e: 1.3125386856487022e-8, + co2e: 1.009645142806694e-8, cost: 10, tags: {}, region: 'us-west1', @@ -1002,7 +1002,7 @@ describe('GCP BillingExportTable Service', () => { accountId: accountId, accountName: accountName, cloudProvider: 'GCP', - co2e: 0.0000012795859171217682, + co2e: 0.0000011569589333975986, cost: 10, tags: {}, region: 'us-east1', @@ -1014,7 +1014,7 @@ describe('GCP BillingExportTable Service', () => { accountId: accountId, accountName: accountName, cloudProvider: 'GCP', - co2e: 0.0000016551207737725232, + co2e: 0.000001532716343557835, cost: 10, kilowattHours: 0.0026609658742323523, tags: {}, @@ -1026,7 +1026,7 @@ describe('GCP BillingExportTable Service', () => { accountId: 'test-account-id', accountName: 'test-account-name', cloudProvider: 'GCP', - co2e: 1.3195092691219494e-8, + co2e: 1.0150071300938072e-8, cost: 10, kilowattHours: 0.00016916785501563453, tags: {}, @@ -1073,7 +1073,7 @@ describe('GCP BillingExportTable Service', () => { accountId: accountId, accountName: accountName, cloudProvider: 'GCP', - co2e: 1.0393783450126647e-18, + co2e: 7.99521803855896e-19, cost: 10, tags: {}, region: 'us-west1', @@ -1085,7 +1085,7 @@ describe('GCP BillingExportTable Service', () => { accountId: accountId, accountName: accountName, cloudProvider: 'GCP', - co2e: 6.0369372367858886e-18, + co2e: 3.132373094558716e-18, cost: 8, tags: {}, region: 'europe-west1', @@ -1097,7 +1097,7 @@ describe('GCP BillingExportTable Service', () => { accountId: accountId, accountName: accountName, cloudProvider: 'GCP', - co2e: 1.9419861336549123e-17, + co2e: 1.9505411386489868e-17, cost: 5, tags: {}, region: 'us-central1', @@ -1144,7 +1144,7 @@ describe('GCP BillingExportTable Service', () => { accountId: accountId, accountName: accountName, cloudProvider: 'GCP', - co2e: 0.000014562404999999998, + co2e: 0.00001120185, cost: 10, tags: {}, region: 'us-west1', @@ -1156,7 +1156,7 @@ describe('GCP BillingExportTable Service', () => { accountId: accountId, accountName: accountName, cloudProvider: 'GCP', - co2e: 0.00005185944, + co2e: 0.0000269082, cost: 8, tags: {}, region: 'europe-west1', @@ -1168,7 +1168,7 @@ describe('GCP BillingExportTable Service', () => { accountId: accountId, accountName: accountName, cloudProvider: 'GCP', - co2e: 0.00003131982, + co2e: 0.00001625085, cost: 8, tags: {}, region: 'europe-west1', @@ -1215,7 +1215,7 @@ describe('GCP BillingExportTable Service', () => { accountId: accountId, accountName: accountName, cloudProvider: 'GCP', - co2e: 0.003249840474753334, + co2e: 0.002938397429256139, cost: 456, tags: {}, region: 'us-east1', @@ -1227,7 +1227,7 @@ describe('GCP BillingExportTable Service', () => { accountId: accountId, accountName: accountName, cloudProvider: 'GCP', - co2e: 0.15353438643927866, + co2e: 0.13882067440551446, cost: 6018.6968, tags: {}, region: 'us-east1', @@ -1247,7 +1247,7 @@ describe('GCP BillingExportTable Service', () => { accountId: accountId, accountName: accountName, cloudProvider: 'GCP', - co2e: 0.0000024561671112909644, + co2e: 0.000002220784429792247, cost: 789, tags: {}, region: 'us-east1', @@ -1259,7 +1259,7 @@ describe('GCP BillingExportTable Service', () => { accountId: accountId, accountName: accountName, cloudProvider: 'GCP', - co2e: 8.025428493835144e-18, + co2e: 7.256324929842608e-18, cost: 0.012744, tags: {}, region: 'us-east1', @@ -1279,7 +1279,7 @@ describe('GCP BillingExportTable Service', () => { accountId: accountId, accountName: accountName, cloudProvider: 'GCP', - co2e: 0.0000012795859171217682, + co2e: 0.0000011569589333975986, cost: 123, tags: {}, region: 'us-east1', @@ -1291,7 +1291,7 @@ describe('GCP BillingExportTable Service', () => { accountId: accountId, accountName: accountName, cloudProvider: 'GCP', - co2e: 0.000003604108730299473, + co2e: 0.0000032587149769791065, cost: 0.816998, tags: {}, region: 'us-east1', @@ -1311,7 +1311,7 @@ describe('GCP BillingExportTable Service', () => { accountId: accountId, accountName: accountName, cloudProvider: 'GCP', - co2e: 5.2944844961166387e-17, + co2e: 4.787096398572127e-17, cost: 10, tags: {}, region: 'us-east1', @@ -1323,7 +1323,7 @@ describe('GCP BillingExportTable Service', () => { accountId: accountId, accountName: accountName, cloudProvider: 'GCP', - co2e: 4.1842706470923184e-13, + co2e: 3.7832780434126373e-13, cost: 25, tags: {}, region: 'us-east1', @@ -1402,7 +1402,7 @@ describe('GCP BillingExportTable Service', () => { const expectedResult: LookupTableOutput[] = [ { - co2e: 9.510545e-7, + co2e: 8.140604999999999e-7, kilowattHours: 0.0026345, machineType: '', region: 'us-east4', @@ -1410,23 +1410,23 @@ describe('GCP BillingExportTable Service', () => { usageType: 'N1 Predefined Instance Core running in Virginia', }, { - co2e: 0.000005057275113774734, - kilowattHours: 0.014009072337326133, + co2e: 0.0000044006191137747345, + kilowattHours: 0.014241485805096227, machineType: 'n1-standard-4', region: 'us-east4', serviceName: 'Compute Engine', usageType: 'N1 Predefined Instance Core running in Virginia', }, { - co2e: 0.000009615983113774734, - kilowattHours: 0.026637072337326137, + co2e: 0.000008302671113774734, + kilowattHours: 0.02686948580509623, machineType: 'n1-standard-8', region: 'us-east4', serviceName: 'Compute Engine', usageType: 'N1 Predefined Instance Core running in Virginia', }, { - co2e: 1.3535463949665428e-19, + co2e: 7.023118087090553e-20, kilowattHours: 6.384652806445957e-16, machineType: '', region: 'europe-west1', @@ -1434,7 +1434,7 @@ describe('GCP BillingExportTable Service', () => { usageType: 'Storage PD Capacity', }, { - co2e: 7.67648220062256e-13, + co2e: 3.983080387115479e-13, kilowattHours: 3.6209821701049808e-9, machineType: '', region: 'europe-west1', @@ -1442,7 +1442,7 @@ describe('GCP BillingExportTable Service', () => { usageType: 'Network Internet Egress from EMEA to Americas', }, { - co2e: 5.499969120137394e-19, + co2e: 5.524198058992625e-19, kilowattHours: 1.2114469427615405e-15, machineType: '', region: 'us-central1', @@ -1450,7 +1450,7 @@ describe('GCP BillingExportTable Service', () => { usageType: 'SSD backed PD Capacity', }, { - co2e: 0.0000048757261666666675, + co2e: 0.000004173405500000001, kilowattHours: 0.01350616666666667, machineType: '', region: 'us-east4', @@ -1458,7 +1458,7 @@ describe('GCP BillingExportTable Service', () => { usageType: 'Backend Instances', }, { - co2e: 1.307174563407898e-12, + co2e: 1.1188834905624389e-12, kilowattHours: 3.6209821701049804e-9, machineType: '', region: 'us-east4', @@ -1506,7 +1506,7 @@ describe('GCP BillingExportTable Service', () => { serviceEstimates: [ { kilowattHours: 0.00512128634808404, - co2e: 0.0000024582174470803393, + co2e: 0.000002222638275068473, usesAverageCPUConstant: false, cloudProvider: 'GCP', accountId: accountId, @@ -1523,7 +1523,7 @@ describe('GCP BillingExportTable Service', () => { accountId: 'test-account-id', accountName: 'test-account-name', cloudProvider: 'GCP', - co2e: 0.00002815242666666667, + co2e: 0.00002545448577777778, cost: 7, kilowattHours: 0.05865088888888889, region: 'us-east1', diff --git a/packages/gcp/src/__tests__/Recommendations.test.ts b/packages/gcp/src/__tests__/Recommendations.test.ts index c65bc5a67..20d6d9a90 100644 --- a/packages/gcp/src/__tests__/Recommendations.test.ts +++ b/packages/gcp/src/__tests__/Recommendations.test.ts @@ -159,7 +159,7 @@ describe('GCP Recommendations Service', () => { recommendationDetail: "Save cost by stopping Idle VM 'test-instance'.", kilowattHourSavings: 58.152384000000005, - co2eSavings: 0.0045358859520000004, + co2eSavings: 0.0034891430400000004, costSavings: 15, instanceName: 'test-resource-name', resourceId: '12456789012', @@ -203,7 +203,7 @@ describe('GCP Recommendations Service', () => { recommendationDetail: "Save cost by stopping Idle VM 'test-instance'.", kilowattHourSavings: 58.41792000000001, - co2eSavings: 0.024046546771602437, + co2eSavings: 0.0185270715092137, costSavings: 55, instanceName: 'test-resource-name', resourceId: '12456789012', @@ -245,7 +245,7 @@ describe('GCP Recommendations Service', () => { recommendationDetail: "Save cost by stopping Idle VM 'test-instance'.", kilowattHourSavings: 58.152384000000005, - co2eSavings: 0.0045358859520000004, + co2eSavings: 0.0034891430400000004, costSavings: 15, instanceName: 'test-resource-name', resourceId: '12456789012', @@ -322,7 +322,7 @@ describe('GCP Recommendations Service', () => { recommendationDetail: "Save cost by stopping Idle VM 'test-instance'.", kilowattHourSavings: 58.1626332, - co2eSavings: 0.0045366853896, + co2eSavings: 0.0034897579920000004, costSavings: 15, instanceName: 'test-instance-name', resourceId: '12456789012', @@ -368,7 +368,7 @@ describe('GCP Recommendations Service', () => { recommendationDetail: "Save cost by stopping Idle VM 'test-instance'.", kilowattHourSavings: 58.18155480000001, - co2eSavings: 0.004538161274400001, + co2eSavings: 0.0034908932880000003, costSavings: 15, instanceName: 'test-instance-name', resourceId: '12456789012', @@ -412,7 +412,7 @@ describe('GCP Recommendations Service', () => { recommendationDetail: 'Save cost by changing machine type from e2-medium to e2-small.', kilowattHourSavings: 1.6960454999999999, - co2eSavings: 0.00013229154899999999, + co2eSavings: 0.00010176272999999999, costSavings: 20, resourceId: '12456789012', instanceName: 'test-resource-name', @@ -456,7 +456,7 @@ describe('GCP Recommendations Service', () => { recommendationDetail: 'Save cost by changing machine type from e2-medium to e2-small.', kilowattHourSavings: 1.6960454999999999, - co2eSavings: 0.00013229154899999999, + co2eSavings: 0.00010176272999999999, costSavings: 20, resourceId: '', instanceName: 'instance-name', @@ -492,7 +492,7 @@ describe('GCP Recommendations Service', () => { recommendationDetail: "Save cost by deleting idle persistent disk 'test-disk'.", kilowattHourSavings: 0.0189216, - co2eSavings: 0.0000014758848, + co2eSavings: 0.000001135296, costSavings: 50, resourceId: '12456789012', instanceName: 'test-resource-name', @@ -528,7 +528,7 @@ describe('GCP Recommendations Service', () => { recommendationDetail: "Save cost by deleting idle persistent disk 'test-disk'.", kilowattHourSavings: 0.010249200000000002, - co2eSavings: 7.994376000000002e-7, + co2eSavings: 6.149520000000001e-7, costSavings: 50, resourceId: '12456789012', instanceName: 'test-resource-name', @@ -563,7 +563,7 @@ describe('GCP Recommendations Service', () => { recommendationType: 'DELETE_IMAGE', recommendationDetail: "Save cost by deleting idle image 'test-image'.", kilowattHourSavings: 0.0002771527420842647, - co2eSavings: 2.1617913882572647e-8, + co2eSavings: 1.6629164525055884e-8, costSavings: 30, resourceId: '12456789012', instanceName: 'test-resource-name', @@ -637,7 +637,7 @@ describe('GCP Recommendations Service', () => { recommendationType: 'STOP_VM', recommendationDetail: "Save cost by stopping Idle VM 'test-instance'.", kilowattHourSavings: 58.152384000000005, - co2eSavings: 0.0045358859520000004, + co2eSavings: 0.0034891430400000004, costSavings: 15, instanceName: 'test-resource-name', resourceId: '12456789012', @@ -651,7 +651,7 @@ describe('GCP Recommendations Service', () => { recommendationDetail: "Save cost by deleting idle address 'test-address'.", kilowattHourSavings: 155.07302400000003, - co2eSavings: 0.012095695872000002, + co2eSavings: 0.009304381440000002, costSavings: 40, resourceId: '123456789012345', instanceName: 'test-address', @@ -665,7 +665,7 @@ describe('GCP Recommendations Service', () => { recommendationDetail: "Save cost by deleting idle address 'test-address'.", kilowattHourSavings: 155.07302400000003, - co2eSavings: 0.07443505152000002, + co2eSavings: 0.06730169241600001, costSavings: 40, resourceId: '123456789012345', instanceName: 'test-address', diff --git a/packages/gcp/src/domain/GcpFootprintEstimationConstants.ts b/packages/gcp/src/domain/GcpFootprintEstimationConstants.ts index df9317aa2..12de8c6fb 100644 --- a/packages/gcp/src/domain/GcpFootprintEstimationConstants.ts +++ b/packages/gcp/src/domain/GcpFootprintEstimationConstants.ts @@ -144,78 +144,92 @@ export const getGCPEmissionsFactors = (): CloudConstantsEmissionsFactors => { // These emission factors take into account Google Carbon Free Energy percentage in each region. Source: https://cloud.google.com/sustainability/region-carbon if (configLoader().GCP.USE_CARBON_FREE_ENERGY_PERCENTAGE) return { - [GCP_REGIONS.US_CENTRAL1]: 0.00003178, - [GCP_REGIONS.US_CENTRAL2]: 0.00003178, - [GCP_REGIONS.US_EAST1]: 0.0003504, - [GCP_REGIONS.US_EAST4]: 0.00015162, - [GCP_REGIONS.US_WEST1]: 0.0000078, - [GCP_REGIONS.US_WEST2]: 0.00011638, - [GCP_REGIONS.US_WEST3]: 0.00038376, - [GCP_REGIONS.US_WEST4]: 0.00036855, - [GCP_REGIONS.ASIA_EAST1]: 0.0004428, - [GCP_REGIONS.ASIA_EAST2]: 0.000453, - [GCP_REGIONS.ASIA_NORTHEAST1]: 0.00048752, - [GCP_REGIONS.ASIA_NORTHEAST2]: 0.000442, - [GCP_REGIONS.ASIA_NORTHEAST3]: 0.00031533, - [GCP_REGIONS.ASIA_SOUTH1]: 0.00063448, - [GCP_REGIONS.ASIA_SOUTH2]: 0.000657, - [GCP_REGIONS.ASIA_SOUTHEAST1]: 0.00047328, - [GCP_REGIONS.ASIA_SOUTHEAST2]: 0.000647, - [GCP_REGIONS.AUSTRALIA_SOUTHEAST1]: 0.00064703, - [GCP_REGIONS.AUSTRALIA_SOUTHEAST2]: 0.000691, - [GCP_REGIONS.EUROPE_CENTRAL2]: 0.000622, - [GCP_REGIONS.EUROPE_NORTH1]: 0.00000798, - [GCP_REGIONS.EUROPE_WEST1]: 0.00004452, - [GCP_REGIONS.EUROPE_WEST2]: 0.00009471, - [GCP_REGIONS.EUROPE_WEST3]: 0.00010841, - [GCP_REGIONS.EUROPE_WEST4]: 0.000164, - [GCP_REGIONS.EUROPE_WEST6]: 0.000087, - [GCP_REGIONS.NORTHAMERICA_NORTHEAST1]: 0.000027, - [GCP_REGIONS.SOUTHAMERICA_EAST1]: 0.00001236, + [GCP_REGIONS.US_CENTRAL1]: 0.0002152373529, + [GCP_REGIONS.US_CENTRAL2]: 0.0002152373529, + [GCP_REGIONS.US_EAST1]: 0.0003255, + [GCP_REGIONS.US_EAST4]: 0.00011124, + [GCP_REGIONS.US_EAST5]: 0.00011124, + [GCP_REGIONS.US_WEST1]: 0.0000072, + [GCP_REGIONS.US_WEST2]: 0.0000893, + [GCP_REGIONS.US_WEST3]: 0.00030912, + [GCP_REGIONS.US_WEST4]: 0.00028835, + [GCP_REGIONS.US_SOUTH1]: 0.0001776, + [GCP_REGIONS.ASIA_EAST1]: 0.00037848, + [GCP_REGIONS.ASIA_EAST2]: 0.0002592, + [GCP_REGIONS.ASIA_NORTHEAST1]: 0.00038976, + [GCP_REGIONS.ASIA_NORTHEAST2]: 0.00026496, + [GCP_REGIONS.ASIA_NORTHEAST3]: 0.00029325, + [GCP_REGIONS.ASIA_SOUTH1]: 0.000603, + [GCP_REGIONS.ASIA_SOUTH2]: 0.00061732, + [GCP_REGIONS.ASIA_SOUTHEAST1]: 0.00035712, + [GCP_REGIONS.ASIA_SOUTHEAST2]: 0.0005046, + [GCP_REGIONS.AUSTRALIA_SOUTHEAST1]: 0.00047242, + [GCP_REGIONS.AUSTRALIA_SOUTHEAST2]: 0.00035949, + [GCP_REGIONS.EUROPE_CENTRAL2]: 0.0004608, + [GCP_REGIONS.EUROPE_NORTH1]: 0.00001143, + [GCP_REGIONS.EUROPE_SOUTHWEST1]: 0.000121, + [GCP_REGIONS.EUROPE_WEST1]: 0.0000198, + [GCP_REGIONS.EUROPE_WEST2]: 0.00007396, + [GCP_REGIONS.EUROPE_WEST3]: 0.0001076, + [GCP_REGIONS.EUROPE_WEST4]: 0.00013301, + [GCP_REGIONS.EUROPE_WEST6]: 0.0000129, + [GCP_REGIONS.EUROPE_WEST8]: 0.000298, + [GCP_REGIONS.EUROPE_WEST9]: 0.000059, + [GCP_REGIONS.NORTHAMERICA_NORTHEAST1]: 0, // Montreal is 100% CFE + [GCP_REGIONS.NORTHAMERICA_NORTHEAST2]: 0.00000232, + [GCP_REGIONS.SOUTHAMERICA_EAST1]: 0.00002838, + [GCP_REGIONS.SOUTHAMERICA_WEST1]: 0.0000589, [GCP_DUAL_REGIONS.ASIA1]: 0.00046476, [GCP_DUAL_REGIONS.EUR4]: 0.00008599, [GCP_DUAL_REGIONS.NAM4]: 0.00019109, [GCP_MULTI_REGIONS.ASIA]: 0.0005058233333, [GCP_MULTI_REGIONS.EU]: 0.0001723183333, [GCP_MULTI_REGIONS.US]: 0.00018025875, - [GCP_REGIONS.UNKNOWN]: 0.0003136559259, // Average across all regions (excluding multi and dual regions) + [GCP_REGIONS.UNKNOWN]: 0.0002152373529, // Average across all regions (excluding multi and dual regions) } // These emissions factors don't take into account Google's CFE%, and just use the Grid emissions factors published by Google. return { - [GCP_REGIONS.US_CENTRAL1]: 0.000454, - [GCP_REGIONS.US_CENTRAL2]: 0.000454, - [GCP_REGIONS.US_EAST1]: 0.00048, - [GCP_REGIONS.US_EAST4]: 0.000361, - [GCP_REGIONS.US_WEST1]: 0.000078, - [GCP_REGIONS.US_WEST2]: 0.000253, - [GCP_REGIONS.US_WEST3]: 0.000533, - [GCP_REGIONS.US_WEST4]: 0.000455, - [GCP_REGIONS.ASIA_EAST1]: 0.00054, - [GCP_REGIONS.ASIA_EAST2]: 0.000453, - [GCP_REGIONS.ASIA_NORTHEAST1]: 0.000554, - [GCP_REGIONS.ASIA_NORTHEAST2]: 0.000442, - [GCP_REGIONS.ASIA_NORTHEAST3]: 0.000457, - [GCP_REGIONS.ASIA_SOUTH1]: 0.000721, - [GCP_REGIONS.ASIA_SOUTH2]: 0.000657, - [GCP_REGIONS.ASIA_SOUTHEAST1]: 0.000493, - [GCP_REGIONS.ASIA_SOUTHEAST2]: 0.000647, - [GCP_REGIONS.AUSTRALIA_SOUTHEAST1]: 0.000727, - [GCP_REGIONS.AUSTRALIA_SOUTHEAST2]: 0.000691, - [GCP_REGIONS.EUROPE_CENTRAL2]: 0.000622, - [GCP_REGIONS.EUROPE_NORTH1]: 0.000133, - [GCP_REGIONS.EUROPE_WEST1]: 0.000212, - [GCP_REGIONS.EUROPE_WEST2]: 0.000231, - [GCP_REGIONS.EUROPE_WEST3]: 0.000293, - [GCP_REGIONS.EUROPE_WEST4]: 0.00041, - [GCP_REGIONS.EUROPE_WEST6]: 0.000087, - [GCP_REGIONS.NORTHAMERICA_NORTHEAST1]: 0.000027, - [GCP_REGIONS.SOUTHAMERICA_EAST1]: 0.000103, + [GCP_REGIONS.US_CENTRAL1]: 0.000456, + [GCP_REGIONS.US_CENTRAL2]: 0.000456, + [GCP_REGIONS.US_EAST1]: 0.000434, + [GCP_REGIONS.US_EAST4]: 0.000309, + [GCP_REGIONS.US_EAST5]: 0.000309, + [GCP_REGIONS.US_WEST1]: 0.00006, + [GCP_REGIONS.US_WEST2]: 0.00019, + [GCP_REGIONS.US_WEST3]: 0.000448, + [GCP_REGIONS.US_WEST4]: 0.000365, + [GCP_REGIONS.US_SOUTH1]: 0.000296, + [GCP_REGIONS.ASIA_EAST1]: 0.000456, + [GCP_REGIONS.ASIA_EAST2]: 0.00036, + [GCP_REGIONS.ASIA_NORTHEAST1]: 0.000464, + [GCP_REGIONS.ASIA_NORTHEAST2]: 0.000384, + [GCP_REGIONS.ASIA_NORTHEAST3]: 0.000425, + [GCP_REGIONS.ASIA_SOUTH1]: 0.00067, + [GCP_REGIONS.ASIA_SOUTH2]: 0.000671, + [GCP_REGIONS.ASIA_SOUTHEAST1]: 0.000372, + [GCP_REGIONS.ASIA_SOUTHEAST2]: 0.00058, + [GCP_REGIONS.AUSTRALIA_SOUTHEAST1]: 0.000598, + [GCP_REGIONS.AUSTRALIA_SOUTHEAST2]: 0.000521, + [GCP_REGIONS.EUROPE_CENTRAL2]: 0.000576, + [GCP_REGIONS.EUROPE_NORTH1]: 0.000127, + [GCP_REGIONS.EUROPE_SOUTHWEST1]: 0.000121, + [GCP_REGIONS.EUROPE_WEST1]: 0.00011, + [GCP_REGIONS.EUROPE_WEST2]: 0.000172, + [GCP_REGIONS.EUROPE_WEST3]: 0.000269, + [GCP_REGIONS.EUROPE_WEST4]: 0.000283, + [GCP_REGIONS.EUROPE_WEST6]: 0.000086, + [GCP_REGIONS.EUROPE_WEST8]: 0.000298, + [GCP_REGIONS.EUROPE_WEST9]: 0.000059, + [GCP_REGIONS.NORTHAMERICA_NORTHEAST1]: 0.000028, + [GCP_REGIONS.NORTHAMERICA_NORTHEAST2]: 0.000029, + [GCP_REGIONS.SOUTHAMERICA_EAST1]: 0.000129, + [GCP_REGIONS.SOUTHAMERICA_WEST1]: 0.00019, [GCP_DUAL_REGIONS.ASIA1]: 0.000498, [GCP_DUAL_REGIONS.EUR4]: 0.0002715, [GCP_DUAL_REGIONS.NAM4]: 0.000467, [GCP_MULTI_REGIONS.ASIA]: 0.0005515555556, [GCP_MULTI_REGIONS.EU]: 0.000284, [GCP_MULTI_REGIONS.US]: 0.0003734285714, - [GCP_REGIONS.UNKNOWN]: 0.0004116296296, // Average of the above regions (excludes multi/dual-regions) + [GCP_REGIONS.UNKNOWN]: 0.0003171470588, // Average of the above regions (excludes multi/dual-regions) } } diff --git a/packages/gcp/src/lib/GCPRegions.ts b/packages/gcp/src/lib/GCPRegions.ts index c48ac2dc7..2abc9ac7d 100644 --- a/packages/gcp/src/lib/GCPRegions.ts +++ b/packages/gcp/src/lib/GCPRegions.ts @@ -16,17 +16,24 @@ export enum GCP_REGIONS { AUSTRALIA_SOUTHEAST2 = 'australia-southeast2', EUROPE_CENTRAL2 = 'europe-central2', EUROPE_NORTH1 = 'europe-north1', + EUROPE_SOUTHWEST1 = 'europe-southwest1', EUROPE_WEST1 = 'europe-west1', EUROPE_WEST2 = 'europe-west2', EUROPE_WEST3 = 'europe-west3', EUROPE_WEST4 = 'europe-west4', EUROPE_WEST6 = 'europe-west6', + EUROPE_WEST8 = 'europe-west8', + EUROPE_WEST9 = 'europe-west9', NORTHAMERICA_NORTHEAST1 = 'northamerica-northeast1', + NORTHAMERICA_NORTHEAST2 = 'northamerica-northeast2', SOUTHAMERICA_EAST1 = 'southamerica-east1', + SOUTHAMERICA_WEST1 = 'southamerica-west1', US_CENTRAL1 = 'us-central1', US_CENTRAL2 = 'us-central2', US_EAST1 = 'us-east1', US_EAST4 = 'us-east4', + US_EAST5 = 'us-east5', + US_SOUTH1 = 'us-south1', US_WEST1 = 'us-west1', US_WEST2 = 'us-west2', US_WEST3 = 'us-west3', From 9927fc5e2825d2a6a146f95b65f6bcd5ff20838a Mon Sep 17 00:00:00 2001 From: Arik Smith Date: Thu, 18 May 2023 18:07:16 -0400 Subject: [PATCH 075/251] update lookup table snapshots --- .../createLookupTable.test.ts.snap | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/packages/cli/src/__tests__/CreateLookupTable/__snapshots__/createLookupTable.test.ts.snap b/packages/cli/src/__tests__/CreateLookupTable/__snapshots__/createLookupTable.test.ts.snap index aa220dfc6..cc528c78c 100644 --- a/packages/cli/src/__tests__/CreateLookupTable/__snapshots__/createLookupTable.test.ts.snap +++ b/packages/cli/src/__tests__/CreateLookupTable/__snapshots__/createLookupTable.test.ts.snap @@ -44,22 +44,22 @@ VPN Gateway,Unknown,VpnGw1,,,0.015215399999999999,0.000005344865126207099 exports[`createLookupTable creates lookup table CSV creates GCP lookup table CSV file, with default output file path 1`] = ` "serviceName,region,usageType,vCpus,machineType,kilowattHours,co2e -Compute Engine,us-east4,N1 Predefined Instance Core running in Virginia,,n1-standard-4,0.014009072337326133,0.000005057275113774734 -Compute Engine,europe-west1,Storage PD Capacity,,,6.384652806445957e-16,1.3535463949665428e-19 -Compute Engine,europe-west1,Network Internet Egress from EMEA to Americas,,,3.6209821701049808e-9,7.67648220062256e-13 -Compute Engine,us-central1,SSD backed PD Capacity,,,1.2114469427615405e-15,5.499969120137394e-19 -App Engine,us-east4,Backend Instances,,,0.012628000000000002,0.000004558708 -Compute Engine,us-east4,Network Inter Region Ingress from Netherlands to Americas,,,3.6209821701049804e-9,1.307174563407898e-12 +Compute Engine,us-east4,N1 Predefined Instance Core running in Virginia,,n1-standard-4,0.014241485805096227,0.0000044006191137747345 +Compute Engine,europe-west1,Storage PD Capacity,,,6.384652806445957e-16,7.023118087090553e-20 +Compute Engine,europe-west1,Network Internet Egress from EMEA to Americas,,,3.6209821701049808e-9,3.983080387115479e-13 +Compute Engine,us-central1,SSD backed PD Capacity,,,1.2114469427615405e-15,5.524198058992625e-19 +App Engine,us-east4,Backend Instances,,,0.012628000000000002,0.000003902052000000001 +Compute Engine,us-east4,Network Inter Region Ingress from Netherlands to Americas,,,3.6209821701049804e-9,1.1188834905624389e-12 " `; exports[`createLookupTable creates lookup table CSV creates GCP lookup table CSV file, with provided output file name 1`] = ` "serviceName,region,usageType,vCpus,machineType,kilowattHours,co2e -Compute Engine,us-east4,N1 Predefined Instance Core running in Virginia,,n1-standard-4,0.014009072337326133,0.000005057275113774734 -Compute Engine,europe-west1,Storage PD Capacity,,,6.384652806445957e-16,1.3535463949665428e-19 -Compute Engine,europe-west1,Network Internet Egress from EMEA to Americas,,,3.6209821701049808e-9,7.67648220062256e-13 -Compute Engine,us-central1,SSD backed PD Capacity,,,1.2114469427615405e-15,5.499969120137394e-19 -App Engine,us-east4,Backend Instances,,,0.012628000000000002,0.000004558708 -Compute Engine,us-east4,Network Inter Region Ingress from Netherlands to Americas,,,3.6209821701049804e-9,1.307174563407898e-12 +Compute Engine,us-east4,N1 Predefined Instance Core running in Virginia,,n1-standard-4,0.014241485805096227,0.0000044006191137747345 +Compute Engine,europe-west1,Storage PD Capacity,,,6.384652806445957e-16,7.023118087090553e-20 +Compute Engine,europe-west1,Network Internet Egress from EMEA to Americas,,,3.6209821701049808e-9,3.983080387115479e-13 +Compute Engine,us-central1,SSD backed PD Capacity,,,1.2114469427615405e-15,5.524198058992625e-19 +App Engine,us-east4,Backend Instances,,,0.012628000000000002,0.000003902052000000001 +Compute Engine,us-east4,Network Inter Region Ingress from Netherlands to Americas,,,3.6209821701049804e-9,1.1188834905624389e-12 " `; From 858e02dd0779267fe83a0f2f63eb602dc5bfd2ff Mon Sep 17 00:00:00 2001 From: Arik Smith Date: Fri, 19 May 2023 15:34:21 -0400 Subject: [PATCH 076/251] update multi-region emissions factors and reduce replication for those regions --- .../src/__tests__/BillingExportTable.test.ts | 12 +++---- .../domain/GcpFootprintEstimationConstants.ts | 32 +++++++++---------- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/packages/gcp/src/__tests__/BillingExportTable.test.ts b/packages/gcp/src/__tests__/BillingExportTable.test.ts index 8ea3ab318..a8437a839 100644 --- a/packages/gcp/src/__tests__/BillingExportTable.test.ts +++ b/packages/gcp/src/__tests__/BillingExportTable.test.ts @@ -235,8 +235,8 @@ describe('GCP BillingExportTable Service', () => { timestamp: new Date('2020-10-28'), serviceEstimates: [ { - kilowattHours: 3.551443417867025e-13, - co2e: 1.6585240761439007e-16, + kilowattHours: 1.7757217089335124e-13, + co2e: 1.4702975749969482e-16, usesAverageCPUConstant: false, cloudProvider: 'GCP', accountId: accountId, @@ -270,9 +270,9 @@ describe('GCP BillingExportTable Service', () => { accountId: accountId, accountName: accountName, cloudProvider: 'GCP', - co2e: 2.0858679274940342e-13, + co2e: 5.222649420999611e-13, cost: 220, - kilowattHours: 5.585721305881937e-10, + kilowattHours: 1.8619071019606457e-10, tags: {}, region: 'us', serviceName: 'Cloud Storage', @@ -342,8 +342,8 @@ describe('GCP BillingExportTable Service', () => { region: 'asia-northeast1', }, { - kilowattHours: 1.3380426046296634e-10, - co2e: 7.380048322129852e-14, + kilowattHours: 4.4601420154322116e-11, + co2e: 7.475198017864387e-14, usesAverageCPUConstant: false, cloudProvider: 'GCP', accountId: accountId, diff --git a/packages/gcp/src/domain/GcpFootprintEstimationConstants.ts b/packages/gcp/src/domain/GcpFootprintEstimationConstants.ts index 12de8c6fb..d13701a0e 100644 --- a/packages/gcp/src/domain/GcpFootprintEstimationConstants.ts +++ b/packages/gcp/src/domain/GcpFootprintEstimationConstants.ts @@ -121,14 +121,14 @@ export const GCP_CLOUD_CONSTANTS: CloudConstantsByProvider = { AVG_CPU_UTILIZATION_2020: 50, REPLICATION_FACTORS: { CLOUD_STORAGE_SINGLE_REGION: 2, - CLOUD_STORAGE_DUAL_REGION: 4, - CLOUD_STORAGE_MULTI_REGION: 6, + CLOUD_STORAGE_DUAL_REGION: 2, + CLOUD_STORAGE_MULTI_REGION: 2, COMPUTE_ENGINE_REGIONAL_DISKS: 2, CLOUD_FILESTORE: 2, CLOUD_SQL_HIGH_AVAILABILITY: 2, CLOUD_MEMORY_STORE_REDIS: 2, - CLOUD_SPANNER_SINGLE_REGION: 4, - CLOUD_SPANNER_MULTI_REGION: 6, + CLOUD_SPANNER_SINGLE_REGION: 2, + CLOUD_SPANNER_MULTI_REGION: 2, KUBERNETES_ENGINE: 3, DEFAULT: 1, }, @@ -179,12 +179,12 @@ export const getGCPEmissionsFactors = (): CloudConstantsEmissionsFactors => { [GCP_REGIONS.NORTHAMERICA_NORTHEAST2]: 0.00000232, [GCP_REGIONS.SOUTHAMERICA_EAST1]: 0.00002838, [GCP_REGIONS.SOUTHAMERICA_WEST1]: 0.0000589, - [GCP_DUAL_REGIONS.ASIA1]: 0.00046476, - [GCP_DUAL_REGIONS.EUR4]: 0.00008599, - [GCP_DUAL_REGIONS.NAM4]: 0.00019109, - [GCP_MULTI_REGIONS.ASIA]: 0.0005058233333, - [GCP_MULTI_REGIONS.EU]: 0.0001723183333, - [GCP_MULTI_REGIONS.US]: 0.00018025875, + [GCP_DUAL_REGIONS.ASIA1]: 0.00065472, // Sum of asia-northeast1 + asia-northeast2 + [GCP_DUAL_REGIONS.EUR4]: 0.00014444, // Sum of europe-west4 + europe-north1 + [GCP_DUAL_REGIONS.NAM4]: 0.00033732, // Sum of us-central1 + us-east1 + [GCP_MULTI_REGIONS.ASIA]: 0.00139032, // Sum of region group data centers within Asia + [GCP_MULTI_REGIONS.EU]: 0.00121064, // Sum of region group data centers within EU + [GCP_MULTI_REGIONS.US]: 0.00143137, // Sum of all US data centers [GCP_REGIONS.UNKNOWN]: 0.0002152373529, // Average across all regions (excluding multi and dual regions) } // These emissions factors don't take into account Google's CFE%, and just use the Grid emissions factors published by Google. @@ -224,12 +224,12 @@ export const getGCPEmissionsFactors = (): CloudConstantsEmissionsFactors => { [GCP_REGIONS.NORTHAMERICA_NORTHEAST2]: 0.000029, [GCP_REGIONS.SOUTHAMERICA_EAST1]: 0.000129, [GCP_REGIONS.SOUTHAMERICA_WEST1]: 0.00019, - [GCP_DUAL_REGIONS.ASIA1]: 0.000498, - [GCP_DUAL_REGIONS.EUR4]: 0.0002715, - [GCP_DUAL_REGIONS.NAM4]: 0.000467, - [GCP_MULTI_REGIONS.ASIA]: 0.0005515555556, - [GCP_MULTI_REGIONS.EU]: 0.000284, - [GCP_MULTI_REGIONS.US]: 0.0003734285714, + [GCP_DUAL_REGIONS.ASIA1]: 0.000848, // Sum of asia-northeast1 + asia-northeast2 + [GCP_DUAL_REGIONS.EUR4]: 0.00041, // Sum of europe-west4 + europe-north1 + [GCP_DUAL_REGIONS.NAM4]: 0.000828, // Sum of us-central1 + us-east1 + [GCP_MULTI_REGIONS.ASIA]: 0.001676, // Sum of region group data centers within Asia + [GCP_MULTI_REGIONS.EU]: 0.001843, // Sum of region group data centers within EU + [GCP_MULTI_REGIONS.US]: 0.002805, // Sum of all US data centers [GCP_REGIONS.UNKNOWN]: 0.0003171470588, // Average of the above regions (excludes multi/dual-regions) } } From 122a14262fceb325b605dabc21ec3f9ce1e43a54 Mon Sep 17 00:00:00 2001 From: Arik Smith Date: Fri, 19 May 2023 16:14:05 -0400 Subject: [PATCH 077/251] update gcp region PUE values --- .../createLookupTable.test.ts.snap | 20 +-- .../src/__tests__/BillingExportTable.test.ts | 156 +++++++++--------- .../gcp/src/__tests__/Recommendations.test.ts | 48 +++--- .../domain/GcpFootprintEstimationConstants.ts | 15 +- 4 files changed, 119 insertions(+), 120 deletions(-) diff --git a/packages/cli/src/__tests__/CreateLookupTable/__snapshots__/createLookupTable.test.ts.snap b/packages/cli/src/__tests__/CreateLookupTable/__snapshots__/createLookupTable.test.ts.snap index cc528c78c..a485c9e83 100644 --- a/packages/cli/src/__tests__/CreateLookupTable/__snapshots__/createLookupTable.test.ts.snap +++ b/packages/cli/src/__tests__/CreateLookupTable/__snapshots__/createLookupTable.test.ts.snap @@ -44,22 +44,22 @@ VPN Gateway,Unknown,VpnGw1,,,0.015215399999999999,0.000005344865126207099 exports[`createLookupTable creates lookup table CSV creates GCP lookup table CSV file, with default output file path 1`] = ` "serviceName,region,usageType,vCpus,machineType,kilowattHours,co2e -Compute Engine,us-east4,N1 Predefined Instance Core running in Virginia,,n1-standard-4,0.014241485805096227,0.0000044006191137747345 -Compute Engine,europe-west1,Storage PD Capacity,,,6.384652806445957e-16,7.023118087090553e-20 -Compute Engine,europe-west1,Network Internet Egress from EMEA to Americas,,,3.6209821701049808e-9,3.983080387115479e-13 +Compute Engine,us-east4,N1 Predefined Instance Core running in Virginia,,n1-standard-4,0.014011885805096226,0.0000043296727137747334 +Compute Engine,europe-west1,Storage PD Capacity,,,6.443769962061198e-16,7.088146958267318e-20 +Compute Engine,europe-west1,Network Internet Egress from EMEA to Americas,,,3.654509782791138e-9,4.019960761070252e-13 Compute Engine,us-central1,SSD backed PD Capacity,,,1.2114469427615405e-15,5.524198058992625e-19 -App Engine,us-east4,Backend Instances,,,0.012628000000000002,0.000003902052000000001 -Compute Engine,us-east4,Network Inter Region Ingress from Netherlands to Americas,,,3.6209821701049804e-9,1.1188834905624389e-12 +App Engine,us-east4,Backend Instances,,,0.0123984,0.0000038311056 +Compute Engine,us-east4,Network Inter Region Ingress from Netherlands to Americas,,,3.6545097827911376e-9,1.1292435228824614e-12 " `; exports[`createLookupTable creates lookup table CSV creates GCP lookup table CSV file, with provided output file name 1`] = ` "serviceName,region,usageType,vCpus,machineType,kilowattHours,co2e -Compute Engine,us-east4,N1 Predefined Instance Core running in Virginia,,n1-standard-4,0.014241485805096227,0.0000044006191137747345 -Compute Engine,europe-west1,Storage PD Capacity,,,6.384652806445957e-16,7.023118087090553e-20 -Compute Engine,europe-west1,Network Internet Egress from EMEA to Americas,,,3.6209821701049808e-9,3.983080387115479e-13 +Compute Engine,us-east4,N1 Predefined Instance Core running in Virginia,,n1-standard-4,0.014011885805096226,0.0000043296727137747334 +Compute Engine,europe-west1,Storage PD Capacity,,,6.443769962061198e-16,7.088146958267318e-20 +Compute Engine,europe-west1,Network Internet Egress from EMEA to Americas,,,3.654509782791138e-9,4.019960761070252e-13 Compute Engine,us-central1,SSD backed PD Capacity,,,1.2114469427615405e-15,5.524198058992625e-19 -App Engine,us-east4,Backend Instances,,,0.012628000000000002,0.000003902052000000001 -Compute Engine,us-east4,Network Inter Region Ingress from Netherlands to Americas,,,3.6209821701049804e-9,1.1188834905624389e-12 +App Engine,us-east4,Backend Instances,,,0.0123984,0.0000038311056 +Compute Engine,us-east4,Network Inter Region Ingress from Netherlands to Americas,,,3.6545097827911376e-9,1.1292435228824614e-12 " `; diff --git a/packages/gcp/src/__tests__/BillingExportTable.test.ts b/packages/gcp/src/__tests__/BillingExportTable.test.ts index a8437a839..2184c3fa5 100644 --- a/packages/gcp/src/__tests__/BillingExportTable.test.ts +++ b/packages/gcp/src/__tests__/BillingExportTable.test.ts @@ -101,8 +101,8 @@ describe('GCP BillingExportTable Service', () => { timestamp: new Date('2020-11-02'), serviceEstimates: [ { - kilowattHours: 0.005190060141275502, - co2e: 0.0000022524861013135677, + kilowattHours: 0.005180640794376637, + co2e: 0.0000022483981047594606, usesAverageCPUConstant: false, cloudProvider: 'GCP', accountId: accountId, @@ -152,8 +152,8 @@ describe('GCP BillingExportTable Service', () => { timestamp: new Date('2020-11-02'), serviceEstimates: [ { - kilowattHours: 1.4232136891223488, - co2e: 0.0006176747410790994, + kilowattHours: 1.4206307241693137, + co2e: 0.0006165537342894821, usesAverageCPUConstant: false, cloudProvider: 'GCP', accountId: accountId, @@ -164,8 +164,8 @@ describe('GCP BillingExportTable Service', () => { region: 'us-east1', }, { - kilowattHours: 0.05865088888888889, - co2e: 0.00002545448577777778, + kilowattHours: 0.058544444444444455, + co2e: 0.000025408288888888893, usesAverageCPUConstant: true, cloudProvider: 'GCP', accountId: accountId, @@ -184,8 +184,8 @@ describe('GCP BillingExportTable Service', () => { timestamp: new Date('2020-10-28'), serviceEstimates: [ { - kilowattHours: 0.1402554516971577, - co2e: 0.000008415327101829462, + kilowattHours: 0.14089588754965615, + co2e: 0.00000845375325297937, usesAverageCPUConstant: false, cloudProvider: 'GCP', accountId: accountId, @@ -318,8 +318,8 @@ describe('GCP BillingExportTable Service', () => { timestamp: new Date('2020-10-28'), serviceEstimates: [ { - kilowattHours: 0.00028446168676757815, - co2e: 1.2345637205712892e-7, + kilowattHours: 0.0002839454223632813, + co2e: 1.232323133056641e-7, usesAverageCPUConstant: false, cloudProvider: 'GCP', accountId: accountId, @@ -378,8 +378,8 @@ describe('GCP BillingExportTable Service', () => { region: 'us-central1', }, { - kilowattHours: 0.00023740234375, - co2e: 7.335732421875e-8, + kilowattHours: 0.0002330859375, + co2e: 7.202355468749999e-8, usesAverageCPUConstant: false, cloudProvider: 'GCP', accountId: accountId, @@ -453,8 +453,8 @@ describe('GCP BillingExportTable Service', () => { region: 'us-central1', }, { - kilowattHours: 0.00003412500000000001, - co2e: 1.5561000000000006e-8, + kilowattHours: 0.0000338203125, + co2e: 1.5422062500000002e-8, usesAverageCPUConstant: false, cloudProvider: 'GCP', accountId: accountId, @@ -504,8 +504,8 @@ describe('GCP BillingExportTable Service', () => { timestamp: new Date('2020-11-02'), serviceEstimates: [ { - kilowattHours: 18.09392953361943, - co2e: 0.007852765417590833, + kilowattHours: 18.070628440241652, + co2e: 0.007842652743064876, usesAverageCPUConstant: true, cloudProvider: 'GCP', accountId: accountId, @@ -516,8 +516,8 @@ describe('GCP BillingExportTable Service', () => { region: 'us-east1', }, { - kilowattHours: 0.2544911532499013, - co2e: 0.000015269469194994078, + kilowattHours: 0.2548100421387902, + co2e: 0.000015288602528327412, usesAverageCPUConstant: true, cloudProvider: 'GCP', accountId: accountId, @@ -536,8 +536,8 @@ describe('GCP BillingExportTable Service', () => { timestamp: new Date('2020-10-28'), serviceEstimates: [ { - kilowattHours: 13.255962888254281, - co2e: 0.005753087893502358, + kilowattHours: 13.243675227657059, + co2e: 0.005747755048803163, usesAverageCPUConstant: true, cloudProvider: 'GCP', accountId: accountId, @@ -746,8 +746,8 @@ describe('GCP BillingExportTable Service', () => { timestamp: new Date('2020-10-28'), serviceEstimates: [ { - kilowattHours: 89.32470187303058, - co2e: 0.03876692061289526, + kilowattHours: 89.16258807652778, + co2e: 0.03869656322521306, usesAverageCPUConstant: true, cloudProvider: 'GCP', accountId: accountId, @@ -761,9 +761,9 @@ describe('GCP BillingExportTable Service', () => { accountId: 'test-account-id', accountName: 'test-account-name', cloudProvider: 'GCP', - co2e: 0.0008011604824231668, + co2e: 0.0007997064706583333, cost: 20, - kilowattHours: 13.352674707052781, + kilowattHours: 13.328441177638888, tags: {}, region: 'us-west1', serviceName: 'Cloud SQL', @@ -810,13 +810,13 @@ describe('GCP BillingExportTable Service', () => { accountId: accountId, accountName: accountName, cloudProvider: 'GCP', - co2e: 0.00036335037541596114, + co2e: 0.0003626909373480556, cost: 190, tags: {}, region: 'us-east1', serviceName: 'Cloud Dataflow', usesAverageCPUConstant: true, - kilowattHours: 0.8372128465805556, + kilowattHours: 0.8356934040277779, }, { accountId: accountId, @@ -834,9 +834,9 @@ describe('GCP BillingExportTable Service', () => { accountId: 'test-account-id', accountName: 'test-account-name', cloudProvider: 'GCP', - co2e: 0.000004629852781905555, + co2e: 0.000004621450145277778, cost: 10, - kilowattHours: 0.010667863552777778, + kilowattHours: 0.01064850263888889, tags: {}, region: 'us-east1', serviceName: 'App Engine', @@ -990,25 +990,25 @@ describe('GCP BillingExportTable Service', () => { accountId: accountId, accountName: accountName, cloudProvider: 'GCP', - co2e: 1.009645142806694e-8, + co2e: 1.014255394600332e-8, cost: 10, tags: {}, region: 'us-west1', serviceName: 'App Engine', usesAverageCPUConstant: false, - kilowattHours: 0.00016827419046778233, + kilowattHours: 0.000169042565766722, }, { accountId: accountId, accountName: accountName, cloudProvider: 'GCP', - co2e: 0.0000011569589333975986, + co2e: 0.0000011548591894168408, cost: 10, tags: {}, region: 'us-east1', serviceName: 'Cloud Storage', usesAverageCPUConstant: false, - kilowattHours: 0.0026658039940036836, + kilowattHours: 0.0026609658742323523, }, { accountId: accountId, @@ -1026,9 +1026,9 @@ describe('GCP BillingExportTable Service', () => { accountId: 'test-account-id', accountName: 'test-account-name', cloudProvider: 'GCP', - co2e: 1.0150071300938072e-8, + co2e: 1.0142553946003321e-8, cost: 10, - kilowattHours: 0.00016916785501563453, + kilowattHours: 0.00016904256576672202, tags: {}, region: 'us-west1', serviceName: 'Compute Engine', @@ -1073,25 +1073,25 @@ describe('GCP BillingExportTable Service', () => { accountId: accountId, accountName: accountName, cloudProvider: 'GCP', - co2e: 7.99521803855896e-19, + co2e: 8.031725883483887e-19, cost: 10, tags: {}, region: 'us-west1', serviceName: 'Compute engine', usesAverageCPUConstant: false, - kilowattHours: 1.3325363397598266e-14, + kilowattHours: 1.3386209805806478e-14, }, { accountId: accountId, accountName: accountName, cloudProvider: 'GCP', - co2e: 3.132373094558716e-18, + co2e: 3.1613765491379627e-18, cost: 8, tags: {}, region: 'europe-west1', serviceName: 'Compute engine', usesAverageCPUConstant: false, - kilowattHours: 2.847611904144287e-14, + kilowattHours: 2.8739786810345115e-14, }, { accountId: accountId, @@ -1144,37 +1144,37 @@ describe('GCP BillingExportTable Service', () => { accountId: accountId, accountName: accountName, cloudProvider: 'GCP', - co2e: 0.00001120185, + co2e: 0.000011253000000000002, cost: 10, tags: {}, region: 'us-west1', serviceName: 'Compute engine', usesAverageCPUConstant: true, - kilowattHours: 0.1866975, + kilowattHours: 0.18755000000000002, }, { accountId: accountId, accountName: accountName, cloudProvider: 'GCP', - co2e: 0.0000269082, + co2e: 0.000027157350000000002, cost: 8, tags: {}, region: 'europe-west1', serviceName: 'Compute engine', usesAverageCPUConstant: true, - kilowattHours: 0.24462, + kilowattHours: 0.24688500000000002, }, { accountId: accountId, accountName: accountName, cloudProvider: 'GCP', - co2e: 0.00001625085, + co2e: 0.000016401320833333334, cost: 8, tags: {}, region: 'europe-west1', serviceName: 'Notebooks', usesAverageCPUConstant: true, - kilowattHours: 0.147735, + kilowattHours: 0.14910291666666667, }, ], groupBy: grouping, @@ -1215,25 +1215,25 @@ describe('GCP BillingExportTable Service', () => { accountId: accountId, accountName: accountName, cloudProvider: 'GCP', - co2e: 0.002938397429256139, + co2e: 0.002933064584556945, cost: 456, tags: {}, region: 'us-east1', serviceName: 'Compute Engine', usesAverageCPUConstant: true, - kilowattHours: 6.770500989069445, + kilowattHours: 6.758213328472223, }, { accountId: accountId, accountName: accountName, cloudProvider: 'GCP', - co2e: 0.13882067440551446, + co2e: 0.13856873125777305, cost: 6018.6968, tags: {}, region: 'us-east1', serviceName: 'App Engine', usesAverageCPUConstant: false, - kilowattHours: 319.86330508183056, + kilowattHours: 319.2827909165278, }, ], groupBy: grouping, @@ -1247,25 +1247,25 @@ describe('GCP BillingExportTable Service', () => { accountId: accountId, accountName: accountName, cloudProvider: 'GCP', - co2e: 0.000002220784429792247, + co2e: 0.000002216753968032188, cost: 789, tags: {}, region: 'us-east1', serviceName: 'App Engine', usesAverageCPUConstant: false, - kilowattHours: 0.005117014815189509, + kilowattHours: 0.005107728036940526, }, { accountId: accountId, accountName: accountName, cloudProvider: 'GCP', - co2e: 7.256324929842608e-18, + co2e: 7.243155556104235e-18, cost: 0.012744, tags: {}, region: 'us-east1', serviceName: 'Stackdriver Monitoring', usesAverageCPUConstant: false, - kilowattHours: 1.6719642695489882e-14, + kilowattHours: 1.6689298516369207e-14, }, ], groupBy: grouping, @@ -1279,25 +1279,25 @@ describe('GCP BillingExportTable Service', () => { accountId: accountId, accountName: accountName, cloudProvider: 'GCP', - co2e: 0.0000011569589333975986, + co2e: 0.0000011548591894168408, cost: 123, tags: {}, region: 'us-east1', serviceName: 'Cloud Storage', usesAverageCPUConstant: false, - kilowattHours: 0.0026658039940036836, + kilowattHours: 0.0026609658742323523, }, { accountId: accountId, accountName: accountName, cloudProvider: 'GCP', - co2e: 0.0000032587149769791065, + co2e: 0.0000032528007937178022, cost: 0.816998, tags: {}, region: 'us-east1', serviceName: 'Cloud Run', usesAverageCPUConstant: false, - kilowattHours: 0.007508559854790569, + kilowattHours: 0.007494932704418899, }, ], groupBy: grouping, @@ -1311,25 +1311,25 @@ describe('GCP BillingExportTable Service', () => { accountId: accountId, accountName: accountName, cloudProvider: 'GCP', - co2e: 4.787096398572127e-17, + co2e: 4.7784083833297094e-17, cost: 10, tags: {}, region: 'us-east1', serviceName: 'Compute Engine', usesAverageCPUConstant: false, - kilowattHours: 1.103017603357633e-13, + kilowattHours: 1.1010157565275828e-13, }, { accountId: accountId, accountName: accountName, cloudProvider: 'GCP', - co2e: 3.7832780434126373e-13, + co2e: 3.77641184006706e-13, cost: 25, tags: {}, region: 'us-east1', serviceName: 'Cloud Run', usesAverageCPUConstant: false, - kilowattHours: 8.717230514775663e-10, + kilowattHours: 8.701409769739769e-10, }, ], groupBy: grouping, @@ -1402,40 +1402,40 @@ describe('GCP BillingExportTable Service', () => { const expectedResult: LookupTableOutput[] = [ { - co2e: 8.140604999999999e-7, - kilowattHours: 0.0026345, + co2e: 7.992594e-7, + kilowattHours: 0.0025866, machineType: '', region: 'us-east4', serviceName: 'Compute Engine', usageType: 'N1 Predefined Instance Core running in Virginia', }, { - co2e: 0.0000044006191137747345, - kilowattHours: 0.014241485805096227, + co2e: 0.0000043296727137747334, + kilowattHours: 0.014011885805096226, machineType: 'n1-standard-4', region: 'us-east4', serviceName: 'Compute Engine', usageType: 'N1 Predefined Instance Core running in Virginia', }, { - co2e: 0.000008302671113774734, - kilowattHours: 0.02686948580509623, + co2e: 0.000008160778313774732, + kilowattHours: 0.026410285805096224, machineType: 'n1-standard-8', region: 'us-east4', serviceName: 'Compute Engine', usageType: 'N1 Predefined Instance Core running in Virginia', }, { - co2e: 7.023118087090553e-20, - kilowattHours: 6.384652806445957e-16, + co2e: 7.088146958267318e-20, + kilowattHours: 6.443769962061198e-16, machineType: '', region: 'europe-west1', serviceName: 'Compute Engine', usageType: 'Storage PD Capacity', }, { - co2e: 3.983080387115479e-13, - kilowattHours: 3.6209821701049808e-9, + co2e: 4.019960761070252e-13, + kilowattHours: 3.654509782791138e-9, machineType: '', region: 'europe-west1', serviceName: 'Compute Engine', @@ -1450,16 +1450,16 @@ describe('GCP BillingExportTable Service', () => { usageType: 'SSD backed PD Capacity', }, { - co2e: 0.000004173405500000001, - kilowattHours: 0.01350616666666667, + co2e: 0.000004097525399999999, + kilowattHours: 0.013260599999999999, machineType: '', region: 'us-east4', serviceName: 'App Engine', usageType: 'Backend Instances', }, { - co2e: 1.1188834905624389e-12, - kilowattHours: 3.6209821701049804e-9, + co2e: 1.1292435228824614e-12, + kilowattHours: 3.6545097827911376e-9, machineType: '', region: 'us-east4', serviceName: 'Compute Engine', @@ -1505,8 +1505,8 @@ describe('GCP BillingExportTable Service', () => { timestamp: new Date('2020-11-02'), serviceEstimates: [ { - kilowattHours: 0.00512128634808404, - co2e: 0.000002222638275068473, + kilowattHours: 0.005111991817506756, + co2e: 0.000002218604448797932, usesAverageCPUConstant: false, cloudProvider: 'GCP', accountId: accountId, @@ -1523,9 +1523,9 @@ describe('GCP BillingExportTable Service', () => { accountId: 'test-account-id', accountName: 'test-account-name', cloudProvider: 'GCP', - co2e: 0.00002545448577777778, + co2e: 0.000025408288888888893, cost: 7, - kilowattHours: 0.05865088888888889, + kilowattHours: 0.058544444444444455, region: 'us-east1', serviceName: 'Compute Engine', tags: { diff --git a/packages/gcp/src/__tests__/Recommendations.test.ts b/packages/gcp/src/__tests__/Recommendations.test.ts index 20d6d9a90..4a00c48d9 100644 --- a/packages/gcp/src/__tests__/Recommendations.test.ts +++ b/packages/gcp/src/__tests__/Recommendations.test.ts @@ -158,8 +158,8 @@ describe('GCP Recommendations Service', () => { recommendationType: 'STOP_VM', recommendationDetail: "Save cost by stopping Idle VM 'test-instance'.", - kilowattHourSavings: 58.152384000000005, - co2eSavings: 0.0034891430400000004, + kilowattHourSavings: 58.41792000000001, + co2eSavings: 0.003505075200000001, costSavings: 15, instanceName: 'test-resource-name', resourceId: '12456789012', @@ -244,8 +244,8 @@ describe('GCP Recommendations Service', () => { recommendationType: 'STOP_VM', recommendationDetail: "Save cost by stopping Idle VM 'test-instance'.", - kilowattHourSavings: 58.152384000000005, - co2eSavings: 0.0034891430400000004, + kilowattHourSavings: 58.41792000000001, + co2eSavings: 0.003505075200000001, costSavings: 15, instanceName: 'test-resource-name', resourceId: '12456789012', @@ -321,8 +321,8 @@ describe('GCP Recommendations Service', () => { recommendationType: 'STOP_VM', recommendationDetail: "Save cost by stopping Idle VM 'test-instance'.", - kilowattHourSavings: 58.1626332, - co2eSavings: 0.0034897579920000004, + kilowattHourSavings: 58.428216000000006, + co2eSavings: 0.003505692960000001, costSavings: 15, instanceName: 'test-instance-name', resourceId: '12456789012', @@ -367,8 +367,8 @@ describe('GCP Recommendations Service', () => { recommendationType: 'STOP_VM', recommendationDetail: "Save cost by stopping Idle VM 'test-instance'.", - kilowattHourSavings: 58.18155480000001, - co2eSavings: 0.0034908932880000003, + kilowattHourSavings: 58.44722400000001, + co2eSavings: 0.003506833440000001, costSavings: 15, instanceName: 'test-instance-name', resourceId: '12456789012', @@ -411,8 +411,8 @@ describe('GCP Recommendations Service', () => { recommendationType: 'CHANGE_MACHINE_TYPE', recommendationDetail: 'Save cost by changing machine type from e2-medium to e2-small.', - kilowattHourSavings: 1.6960454999999999, - co2eSavings: 0.00010176272999999999, + kilowattHourSavings: 1.7037900000000001, + co2eSavings: 0.00010222740000000001, costSavings: 20, resourceId: '12456789012', instanceName: 'test-resource-name', @@ -455,8 +455,8 @@ describe('GCP Recommendations Service', () => { recommendationType: 'CHANGE_MACHINE_TYPE', recommendationDetail: 'Save cost by changing machine type from e2-medium to e2-small.', - kilowattHourSavings: 1.6960454999999999, - co2eSavings: 0.00010176272999999999, + kilowattHourSavings: 1.7037900000000001, + co2eSavings: 0.00010222740000000001, costSavings: 20, resourceId: '', instanceName: 'instance-name', @@ -491,8 +491,8 @@ describe('GCP Recommendations Service', () => { recommendationType: 'DELETE_DISK', recommendationDetail: "Save cost by deleting idle persistent disk 'test-disk'.", - kilowattHourSavings: 0.0189216, - co2eSavings: 0.000001135296, + kilowattHourSavings: 0.019008000000000004, + co2eSavings: 0.0000011404800000000002, costSavings: 50, resourceId: '12456789012', instanceName: 'test-resource-name', @@ -527,8 +527,8 @@ describe('GCP Recommendations Service', () => { recommendationType: 'SNAPSHOT_AND_DELETE_DISK', recommendationDetail: "Save cost by deleting idle persistent disk 'test-disk'.", - kilowattHourSavings: 0.010249200000000002, - co2eSavings: 6.149520000000001e-7, + kilowattHourSavings: 0.010296000000000003, + co2eSavings: 6.177600000000002e-7, costSavings: 50, resourceId: '12456789012', instanceName: 'test-resource-name', @@ -562,8 +562,8 @@ describe('GCP Recommendations Service', () => { region: 'us-west1', recommendationType: 'DELETE_IMAGE', recommendationDetail: "Save cost by deleting idle image 'test-image'.", - kilowattHourSavings: 0.0002771527420842647, - co2eSavings: 1.6629164525055884e-8, + kilowattHourSavings: 0.0002784182797193527, + co2eSavings: 1.6705096783161164e-8, costSavings: 30, resourceId: '12456789012', instanceName: 'test-resource-name', @@ -636,8 +636,8 @@ describe('GCP Recommendations Service', () => { region: 'us-west1', recommendationType: 'STOP_VM', recommendationDetail: "Save cost by stopping Idle VM 'test-instance'.", - kilowattHourSavings: 58.152384000000005, - co2eSavings: 0.0034891430400000004, + kilowattHourSavings: 58.41792000000001, + co2eSavings: 0.003505075200000001, costSavings: 15, instanceName: 'test-resource-name', resourceId: '12456789012', @@ -650,8 +650,8 @@ describe('GCP Recommendations Service', () => { recommendationType: 'DELETE_ADDRESS', recommendationDetail: "Save cost by deleting idle address 'test-address'.", - kilowattHourSavings: 155.07302400000003, - co2eSavings: 0.009304381440000002, + kilowattHourSavings: 155.78112000000002, + co2eSavings: 0.009346867200000001, costSavings: 40, resourceId: '123456789012345', instanceName: 'test-address', @@ -664,8 +664,8 @@ describe('GCP Recommendations Service', () => { recommendationType: 'DELETE_ADDRESS', recommendationDetail: "Save cost by deleting idle address 'test-address'.", - kilowattHourSavings: 155.07302400000003, - co2eSavings: 0.06730169241600001, + kilowattHourSavings: 155.78112000000002, + co2eSavings: 0.06760900608, costSavings: 40, resourceId: '123456789012345', instanceName: 'test-address', diff --git a/packages/gcp/src/domain/GcpFootprintEstimationConstants.ts b/packages/gcp/src/domain/GcpFootprintEstimationConstants.ts index d13701a0e..77cfd2341 100644 --- a/packages/gcp/src/domain/GcpFootprintEstimationConstants.ts +++ b/packages/gcp/src/domain/GcpFootprintEstimationConstants.ts @@ -102,18 +102,17 @@ export const GCP_CLOUD_CONSTANTS: CloudConstantsByProvider = { MEMORY_COEFFICIENT: 0.000392, // kWh / Gb PUE_AVG: 1.1, PUE_TRAILING_TWELVE_MONTH: { - [GCP_REGIONS.US_EAST1]: 1.102, + [GCP_REGIONS.US_EAST4]: 1.08, [GCP_REGIONS.US_CENTRAL1]: 1.11, - [GCP_REGIONS.US_CENTRAL2]: 1.12, - [GCP_REGIONS.US_WEST1]: 1.095, - [GCP_REGIONS.EUROPE_WEST1]: 1.08, - [GCP_REGIONS.EUROPE_WEST4]: 1.09, + [GCP_REGIONS.US_CENTRAL2]: 1.11, + [GCP_REGIONS.EUROPE_WEST1]: 1.09, + [GCP_REGIONS.EUROPE_WEST4]: 1.07, [GCP_REGIONS.EUROPE_NORTH1]: 1.09, - [GCP_REGIONS.ASIA_EAST1]: 1.13, - [GCP_REGIONS.ASIA_SOUTHEAST1]: 1.14, - [GCP_REGIONS.SOUTHAMERICA_EAST1]: 1.09, + [GCP_REGIONS.ASIA_EAST1]: 1.12, + [GCP_REGIONS.ASIA_SOUTHEAST1]: 1.13, }, getPUE: (region: string): number => { + // Return region-specific PUE if available, otherwise use fleet-wide average return GCP_CLOUD_CONSTANTS.PUE_TRAILING_TWELVE_MONTH[region] ? GCP_CLOUD_CONSTANTS.PUE_TRAILING_TWELVE_MONTH[region] : GCP_CLOUD_CONSTANTS.PUE_AVG From 42b2359ecc6fad31fa0ed5ebea77b028b34cb889 Mon Sep 17 00:00:00 2001 From: Arik Smith Date: Fri, 19 May 2023 17:35:50 -0400 Subject: [PATCH 078/251] changeset: Updates emissions factors and PUE constants to latest published values --- .changeset/hungry-spiders-unite.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/hungry-spiders-unite.md diff --git a/.changeset/hungry-spiders-unite.md b/.changeset/hungry-spiders-unite.md new file mode 100644 index 000000000..1d7edcd82 --- /dev/null +++ b/.changeset/hungry-spiders-unite.md @@ -0,0 +1,5 @@ +--- +'@cloud-carbon-footprint/gcp': minor +--- + +Updates emissions factors and PUE constants to latest published values From e0718ce314481c73acf08d702b07bdf638d77226 Mon Sep 17 00:00:00 2001 From: jianglinwang Date: Mon, 13 Mar 2023 17:32:48 +0800 Subject: [PATCH 079/251] add ali package --- packages/ali/.eslintrc.js | 32 ++++ packages/ali/CHANGELOG.md | 0 packages/ali/README.md | 26 +++ packages/ali/jest.config.js | 20 +++ packages/ali/package.json | 83 ++++++++++ packages/ali/src/__tests__/AliAccount.test.ts | 34 ++++ packages/ali/src/application/AliAccount.ts | 31 ++++ packages/ali/src/application/index.ts | 4 + .../domain/AliFootprintEstimationConstants.ts | 152 ++++++++++++++++++ packages/ali/src/domain/index.ts | 7 + packages/ali/src/index.ts | 5 + packages/ali/tsconfig.json | 9 ++ yarn.lock | 34 ++++ 13 files changed, 437 insertions(+) create mode 100644 packages/ali/.eslintrc.js create mode 100644 packages/ali/CHANGELOG.md create mode 100644 packages/ali/README.md create mode 100644 packages/ali/jest.config.js create mode 100644 packages/ali/package.json create mode 100644 packages/ali/src/__tests__/AliAccount.test.ts create mode 100644 packages/ali/src/application/AliAccount.ts create mode 100644 packages/ali/src/application/index.ts create mode 100644 packages/ali/src/domain/AliFootprintEstimationConstants.ts create mode 100644 packages/ali/src/domain/index.ts create mode 100644 packages/ali/src/index.ts create mode 100644 packages/ali/tsconfig.json diff --git a/packages/ali/.eslintrc.js b/packages/ali/.eslintrc.js new file mode 100644 index 000000000..54918617d --- /dev/null +++ b/packages/ali/.eslintrc.js @@ -0,0 +1,32 @@ +/* + * © 2023 Thoughtworks, Inc. + */ + +module.exports = { + parser: '@typescript-eslint/parser', // Specifies the ESLint parser + parserOptions: { + ecmaVersion: 2020, // Allows for the parsing of modern ECMAScript features + sourceType: 'module', // Allows for the use of imports + }, + extends: [ + 'plugin:@typescript-eslint/recommended', // Uses the recommended rules from the @typescript-eslint/eslint-plugin + 'prettier', // Uses eslint-config-prettier to disable ESLint rules from @typescript-eslint/eslint-plugin that would conflict with prettier + 'plugin:prettier/recommended', // Enables eslint-plugin-prettier and eslint-config-prettier. This will display prettier errors as ESLint errors. Make sure this is always the last configuration in the extends array. + ], + plugins: ['unused-imports'], + rules: { + // Place to specify ESLint rules. Can be used to overwrite rules specified from the extended configs + // e.g. "@typescript-eslint/explicit-function-return-type": "off", + 'no-unused-vars': 'off', + 'unused-imports/no-unused-imports-ts': 'error', + '@typescript-eslint/no-unused-vars': [ + 'error', + { + vars: 'all', + varsIgnorePattern: '^_', + args: 'after-used', + argsIgnorePattern: '^_', + }, + ], + }, +} diff --git a/packages/ali/CHANGELOG.md b/packages/ali/CHANGELOG.md new file mode 100644 index 000000000..e69de29bb diff --git a/packages/ali/README.md b/packages/ali/README.md new file mode 100644 index 000000000..8736bee60 --- /dev/null +++ b/packages/ali/README.md @@ -0,0 +1,26 @@ +# @cloud-carbon-footprint/ali + +This package provides the core logic to get cloud usage data and estimate energy and carbon emissions from Amazon Web Services. + +## Installation + +Install the package via npm or Yarn: + +```sh +$ npm install --save @cloud-carbon-footprint/ali +``` + +or + +```sh +$ yarn add @cloud-carbon-footprint/ali +``` + +## Documentation + +- [Cloud Carbon Footprint Readme](https://github.com/cloud-carbon-footprint/cloud-carbon-footprint/blob/trunk/README.md) +- [Cloud Carbon Footprint Documentation](https://github.com/cloud-carbon-footprint/cloud-carbon-footprint/tree/trunk/microsite/docs/README.md) + +## License + +© 2021 Thoughtworks, Inc. diff --git a/packages/ali/jest.config.js b/packages/ali/jest.config.js new file mode 100644 index 000000000..9c277e411 --- /dev/null +++ b/packages/ali/jest.config.js @@ -0,0 +1,20 @@ +/* + * © 2023 Thoughtworks, Inc. + */ + +// eslint-disable-next-line @typescript-eslint/no-var-requires +const baseConfig = require('../../jest.base.config') + +module.exports = { + ...baseConfig, + coverageThreshold: { + global: { + statements: 0, + branches: 0, //todo: increase coverage, it was 88 before + functions: 0, + lines: 0, + }, + }, + testPathIgnorePatterns: ['/src/__tests__/fixtures'], + modulePathIgnorePatterns: ['index.ts'], +} diff --git a/packages/ali/package.json b/packages/ali/package.json new file mode 100644 index 000000000..4ce86a5cc --- /dev/null +++ b/packages/ali/package.json @@ -0,0 +1,83 @@ +{ + "name": "@cloud-carbon-footprint/ali", + "version": "0.14.2", + "license": "Apache-2.0", + "description": "The core logic to get cloud usage data and estimate energy and carbon emissions from Amazon Web Services.", + "main": "src/index.ts", + "types": "src/index.ts", + "publishConfig": { + "main": "dist/index.js" + }, + "homepage": "https://www.cloudcarbonfootprint.org/", + "repository": { + "type": "git", + "url": "git+https://github.com/cloud-carbon-footprint/cloud-carbon-footprint.git", + "directory": "packages/ali" + }, + "keywords": [ + "thoughtworks", + "cloud", + "sustainability", + "climate", + "carbon-emissions", + "carbon-footprint" + ], + "scripts": { + "precommit": "lint-staged --no-stash && yarn test --coverage --runInBand --bail", + "test": "jest", + "type-check": "tsc --noEmit", + "sonar:scan": "sonar-scanner", + "test:watch": "yarn test --coverage --watchAll", + "build": "rimraf dist && yarn build:tsc && yarn prepack && cp package.json dist && yarn postpack", + "build:workspace": "mkdir -p ../../dist-workspace/packages/ali/dist && cp -R dist ../../dist-workspace/packages/ali && mv ../../dist-workspace/packages/ali/dist/package.json ../../dist-workspace/packages/ali", + "build:update": "node ../../scripts/update-package-dependencies.js ali core common", + "prepack": "ts-node ../../scripts/prepack.ts", + "postpack": "ts-node ../../scripts/postpack.ts", + "build:tsc": "tsc --build tsconfig.json", + "build:watch": "onchange 'src/**/*.ts' -- yarn build", + "view:coverage": "serve coverage/lcov-report", + "clean": "rimraf dist && rimraf coverage && rimraf logs", + "lint": "eslint '*/**/*.ts' --quiet", + "lint:fix": "eslint '*/**/*.ts' --quiet --fix" + }, + "dependencies": { + "@cloud-carbon-footprint/common": "^1.9.0", + "@cloud-carbon-footprint/core": "^0.17.0", + "@google-cloud/iam-credentials": "^1.1.1", + "csvtojson": "^2.0.10", + "moment": "^2.29.1", + "ramda": "^0.28.0" + }, + "devDependencies": { + "@types/jest": "^27.4.0", + "@types/jest-when": "^3.5.0", + "@types/node": "^17.0.8", + "@types/source-map-support": "^0.5.3", + "@typescript-eslint/eslint-plugin": "^5.9.0", + "@typescript-eslint/parser": "^5.9.0", + "eslint": "^8.6.0", + "eslint-config-prettier": "^8.3.0", + "eslint-plugin-prettier": "^4.0.0", + "eslint-plugin-unused-imports": "^2.0.0", + "jest": "^27.4.7", + "jest-when": "^3.5.0", + "lint-staged": "^12.1.7", + "onchange": "^7.1.0", + "prettier": "^2.5.1", + "rimraf": "^3.0.2", + "source-map-support": "^0.5.19", + "ts-jest": "^27.1.2", + "ts-node": "^10.4.0", + "ts-node-dev": "^2.0.0", + "typescript": "^4.6.2" + }, + "lint-staged": { + "*.{js,ts}": [ + "yarn lint", + "prettier --write --ignore-unknown --config ../../.prettierrc.json" + ], + "*.{md}": [ + "prettier --write --ignore-unknown --config ../../.prettierrc.json" + ] + } +} diff --git a/packages/ali/src/__tests__/AliAccount.test.ts b/packages/ali/src/__tests__/AliAccount.test.ts new file mode 100644 index 000000000..b49a06fc7 --- /dev/null +++ b/packages/ali/src/__tests__/AliAccount.test.ts @@ -0,0 +1,34 @@ +/* + * © 2023 Thoughtworks, Inc. + */ + +import { GroupBy } from '@cloud-carbon-footprint/common' + +import AliAccount from '../application/AliAccount' + +describe('Ali Account', () => { + const startDate: Date = new Date('2021-01-01') + const endDate: Date = new Date('2021-01-01') + const grouping: GroupBy = GroupBy.day + + it('gets results from getDataForRegions function', async () => { + const aliAccount = new AliAccount() + const results = await aliAccount.getDataForRegions( + startDate, + endDate, + grouping, + ) + + expect(results).toEqual(null) + }) + it('gets results from getDataFromCostAndUsageReports function', async () => { + const aliAccount = new AliAccount() + const results = await aliAccount.getDataFromCostAndUsageReports( + startDate, + endDate, + grouping, + ) + + expect(results).toEqual(null) + }) +}) diff --git a/packages/ali/src/application/AliAccount.ts b/packages/ali/src/application/AliAccount.ts new file mode 100644 index 000000000..4851b797c --- /dev/null +++ b/packages/ali/src/application/AliAccount.ts @@ -0,0 +1,31 @@ +/* + * © 2023 Thoughtworks, Inc. + */ + +import { CloudProviderAccount } from '@cloud-carbon-footprint/core' +import { EstimationResult, GroupBy } from '@cloud-carbon-footprint/common' + +export default class AliAccount extends CloudProviderAccount { + private readonly credentials: any + + constructor() { + super() + } + + async getDataForRegions( + startDate: Date, + endDate: Date, + grouping: GroupBy, + ): Promise { + return null + } + + getDataFromCostAndUsageReports( + startDate: Date, + endDate: Date, + grouping: GroupBy, + ): Promise { + console.log(startDate, endDate, grouping) + return null + } +} diff --git a/packages/ali/src/application/index.ts b/packages/ali/src/application/index.ts new file mode 100644 index 000000000..9a24de737 --- /dev/null +++ b/packages/ali/src/application/index.ts @@ -0,0 +1,4 @@ +/* + * © 2023 Thoughtworks, Inc. + */ +export { default as ALIAccount } from './AliAccount' diff --git a/packages/ali/src/domain/AliFootprintEstimationConstants.ts b/packages/ali/src/domain/AliFootprintEstimationConstants.ts new file mode 100644 index 000000000..c9e48e8a4 --- /dev/null +++ b/packages/ali/src/domain/AliFootprintEstimationConstants.ts @@ -0,0 +1,152 @@ +/* + * © 2023 Thoughtworks, Inc. + */ +import { + CloudConstantsByProvider, + CloudConstantsEmissionsFactors, + COMPUTE_PROCESSOR_TYPES, + EstimateUnknownUsageBy, + getAverage, + getWattsByAverageOrMedian, +} from '@cloud-carbon-footprint/core' + +export const ALI_CLOUD_CONSTANTS: CloudConstantsByProvider = { + SSDCOEFFICIENT: 1.2, // watt hours / terabyte hour + HDDCOEFFICIENT: 0.65, // watt hours / terabyte hour + MEMORY_AVG: 80.69, + MEMORY_BY_COMPUTE_PROCESSOR: { + // gigaBytes / physical chip + [COMPUTE_PROCESSOR_TYPES.CASCADE_LAKE]: 98.12, + [COMPUTE_PROCESSOR_TYPES.SKYLAKE]: 81.32, + [COMPUTE_PROCESSOR_TYPES.BROADWELL]: 69.65, + [COMPUTE_PROCESSOR_TYPES.HASWELL]: 27.71, + [COMPUTE_PROCESSOR_TYPES.COFFEE_LAKE]: 19.56, + [COMPUTE_PROCESSOR_TYPES.SANDY_BRIDGE]: 16.7, + [COMPUTE_PROCESSOR_TYPES.IVY_BRIDGE]: 9.67, + [COMPUTE_PROCESSOR_TYPES.AMD_EPYC_1ST_GEN]: 89.6, + [COMPUTE_PROCESSOR_TYPES.AMD_EPYC_2ND_GEN]: 129.78, + [COMPUTE_PROCESSOR_TYPES.AWS_GRAVITON_2]: 129.78, + }, + getMemory: (computeProcessors: string[]): number => { + const memoryForProcessors: number[] = computeProcessors.map( + (processor: string) => { + return ALI_CLOUD_CONSTANTS.MEMORY_BY_COMPUTE_PROCESSOR[processor] + }, + ) + const averageMemoryForProcessors = getAverage(memoryForProcessors) + return averageMemoryForProcessors + ? averageMemoryForProcessors + : ALI_CLOUD_CONSTANTS.MEMORY_AVG + }, + MIN_WATTS_AVG: 0.74, + MIN_WATTS_BY_COMPUTE_PROCESSOR: { + // CPUs + [COMPUTE_PROCESSOR_TYPES.CASCADE_LAKE]: 0.64, + [COMPUTE_PROCESSOR_TYPES.SKYLAKE]: 0.65, + [COMPUTE_PROCESSOR_TYPES.BROADWELL]: 0.71, + [COMPUTE_PROCESSOR_TYPES.HASWELL]: 1, + [COMPUTE_PROCESSOR_TYPES.COFFEE_LAKE]: 1.14, + [COMPUTE_PROCESSOR_TYPES.SANDY_BRIDGE]: 2.17, + [COMPUTE_PROCESSOR_TYPES.IVY_BRIDGE]: 3.04, + [COMPUTE_PROCESSOR_TYPES.AMD_EPYC_1ST_GEN]: 0.82, + [COMPUTE_PROCESSOR_TYPES.AMD_EPYC_2ND_GEN]: 0.47, + [COMPUTE_PROCESSOR_TYPES.AWS_GRAVITON_2]: 0.47, + // GPUs + [COMPUTE_PROCESSOR_TYPES.NVIDIA_K520]: 26, + [COMPUTE_PROCESSOR_TYPES.NVIDIA_A10G]: 18, + [COMPUTE_PROCESSOR_TYPES.NVIDIA_T4]: 8, + [COMPUTE_PROCESSOR_TYPES.NVIDIA_TESLA_M60]: 35, + [COMPUTE_PROCESSOR_TYPES.NVIDIA_TESLA_K80]: 35, + [COMPUTE_PROCESSOR_TYPES.NVIDIA_TESLA_V100]: 35, + [COMPUTE_PROCESSOR_TYPES.NVIDIA_TESLA_A100]: 46, + [COMPUTE_PROCESSOR_TYPES.NVIDIA_TESLA_P4]: 9, + [COMPUTE_PROCESSOR_TYPES.NVIDIA_TESLA_P100]: 36, + [COMPUTE_PROCESSOR_TYPES.AMD_RADEON_PRO_V520]: 26, + }, + getMinWatts: (computeProcessors: string[]): number => { + const minWattsForProcessors: number[] = computeProcessors.map( + (processor: string) => { + return ALI_CLOUD_CONSTANTS.MIN_WATTS_BY_COMPUTE_PROCESSOR[processor] + }, + ) + const averageWattsForProcessors = getWattsByAverageOrMedian( + computeProcessors, + minWattsForProcessors, + ) + return averageWattsForProcessors + ? averageWattsForProcessors + : ALI_CLOUD_CONSTANTS.MIN_WATTS_AVG + }, + MAX_WATTS_AVG: 3.5, + MAX_WATTS_BY_COMPUTE_PROCESSOR: { + // CPUs + [COMPUTE_PROCESSOR_TYPES.CASCADE_LAKE]: 3.97, + [COMPUTE_PROCESSOR_TYPES.SKYLAKE]: 4.26, + [COMPUTE_PROCESSOR_TYPES.BROADWELL]: 3.69, + [COMPUTE_PROCESSOR_TYPES.HASWELL]: 4.74, + [COMPUTE_PROCESSOR_TYPES.COFFEE_LAKE]: 5.42, + [COMPUTE_PROCESSOR_TYPES.SANDY_BRIDGE]: 8.58, + [COMPUTE_PROCESSOR_TYPES.IVY_BRIDGE]: 8.25, + [COMPUTE_PROCESSOR_TYPES.AMD_EPYC_1ST_GEN]: 2.55, + [COMPUTE_PROCESSOR_TYPES.AMD_EPYC_2ND_GEN]: 1.69, + [COMPUTE_PROCESSOR_TYPES.AWS_GRAVITON_2]: 1.69, + // GPUs + [COMPUTE_PROCESSOR_TYPES.NVIDIA_K520]: 229, + [COMPUTE_PROCESSOR_TYPES.NVIDIA_A10G]: 153, + [COMPUTE_PROCESSOR_TYPES.NVIDIA_T4]: 71, + [COMPUTE_PROCESSOR_TYPES.NVIDIA_TESLA_M60]: 306, + [COMPUTE_PROCESSOR_TYPES.NVIDIA_TESLA_K80]: 306, + [COMPUTE_PROCESSOR_TYPES.NVIDIA_TESLA_V100]: 306, + [COMPUTE_PROCESSOR_TYPES.NVIDIA_TESLA_A100]: 407, + [COMPUTE_PROCESSOR_TYPES.NVIDIA_TESLA_P4]: 76.5, + [COMPUTE_PROCESSOR_TYPES.NVIDIA_TESLA_P100]: 306, + [COMPUTE_PROCESSOR_TYPES.AMD_RADEON_PRO_V520]: 229, + }, + getMaxWatts: (computeProcessors: string[]): number => { + const maxWattsForProcessors: number[] = computeProcessors.map( + (processor: string) => { + return ALI_CLOUD_CONSTANTS.MAX_WATTS_BY_COMPUTE_PROCESSOR[processor] + }, + ) + const averageWattsForProcessors = getWattsByAverageOrMedian( + computeProcessors, + maxWattsForProcessors, + ) + return averageWattsForProcessors + ? averageWattsForProcessors + : ALI_CLOUD_CONSTANTS.MAX_WATTS_AVG + }, + NETWORKING_COEFFICIENT: 0.001, // kWh / Gb + MEMORY_COEFFICIENT: 0.000392, // kWh / Gb + PUE_AVG: 1.135, + getPUE: (): number => { + return ALI_CLOUD_CONSTANTS.PUE_AVG + }, + AVG_CPU_UTILIZATION_2020: 50, + REPLICATION_FACTORS: { + S3: 6, + S3_ONE_ZONE_REDUCED_REDUNDANCY: 2, + EC2_EBS_VOLUME: 2, + EC2_EBS_SNAPSHOT: 3, + EFS: 3, + EFS_ONE_ZONE: 2, + RDS_BACKUP: 3, + RDS_AURORA: 6, + RDS_MULTI_AZ: 2, + DOCUMENT_DB_BACKUP: 3, + DOCUMENT_DB_STORAGE: 2, + DYNAMO_DB: 2, + ECR_STORAGE: 3, + DOCUMENT_ELASTICACHE_BACKUP: 3, + SIMPLE_DB: 2, + DEFAULT: 1, + }, + KILOWATT_HOURS_BY_SERVICE_AND_USAGE_UNIT: { + total: {}, + }, + ESTIMATE_UNKNOWN_USAGE_BY: EstimateUnknownUsageBy.COST, + SERVER_EXPECTED_LIFESPAN: 35040, // 4 years in hours +} + +export const AWS_EMISSIONS_FACTORS_METRIC_TON_PER_KWH: CloudConstantsEmissionsFactors = + {} diff --git a/packages/ali/src/domain/index.ts b/packages/ali/src/domain/index.ts new file mode 100644 index 000000000..2fdebb950 --- /dev/null +++ b/packages/ali/src/domain/index.ts @@ -0,0 +1,7 @@ +/* + * © 2023 Thoughtworks, Inc. + */ +export { + AWS_EMISSIONS_FACTORS_METRIC_TON_PER_KWH, + ALI_CLOUD_CONSTANTS, +} from './AliFootprintEstimationConstants' diff --git a/packages/ali/src/index.ts b/packages/ali/src/index.ts new file mode 100644 index 000000000..7c6922b0c --- /dev/null +++ b/packages/ali/src/index.ts @@ -0,0 +1,5 @@ +/* + * © 2023 Thoughtworks, Inc. + */ +export * from './application' +export * from './domain' diff --git a/packages/ali/tsconfig.json b/packages/ali/tsconfig.json new file mode 100644 index 000000000..53de7d253 --- /dev/null +++ b/packages/ali/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "rootDir": "./src", + "baseUrl": ".", + "outDir": "./dist" + }, + "include": ["./src"] +} diff --git a/yarn.lock b/yarn.lock index 9a9aed373..aa40c54eb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2204,6 +2204,40 @@ __metadata: languageName: node linkType: hard +"@cloud-carbon-footprint/ali@workspace:packages/ali": + version: 0.0.0-use.local + resolution: "@cloud-carbon-footprint/ali@workspace:packages/ali" + dependencies: + "@cloud-carbon-footprint/common": ^1.9.0 + "@cloud-carbon-footprint/core": ^0.17.0 + "@google-cloud/iam-credentials": ^1.1.1 + "@types/jest": ^27.4.0 + "@types/jest-when": ^3.5.0 + "@types/node": ^17.0.8 + "@types/source-map-support": ^0.5.3 + "@typescript-eslint/eslint-plugin": ^5.9.0 + "@typescript-eslint/parser": ^5.9.0 + csvtojson: ^2.0.10 + eslint: ^8.6.0 + eslint-config-prettier: ^8.3.0 + eslint-plugin-prettier: ^4.0.0 + eslint-plugin-unused-imports: ^2.0.0 + jest: ^27.4.7 + jest-when: ^3.5.0 + lint-staged: ^12.1.7 + moment: ^2.29.1 + onchange: ^7.1.0 + prettier: ^2.5.1 + ramda: ^0.28.0 + rimraf: ^3.0.2 + source-map-support: ^0.5.19 + ts-jest: ^27.1.2 + ts-node: ^10.4.0 + ts-node-dev: ^2.0.0 + typescript: ^4.6.2 + languageName: unknown + linkType: soft + "@cloud-carbon-footprint/api@workspace:packages/api": version: 0.0.0-use.local resolution: "@cloud-carbon-footprint/api@workspace:packages/api" From df78d3bf5ea8b1c8d134774dd43c658a47c1f8b5 Mon Sep 17 00:00:00 2001 From: jianglinwang Date: Tue, 14 Mar 2023 09:19:57 +0800 Subject: [PATCH 080/251] add ali config --- .../domain/AliFootprintEstimationConstants.ts | 2 +- packages/ali/src/domain/index.ts | 2 +- packages/api/.env.template | 5 ++++ packages/app/src/App.ts | 19 ++++++++++++--- packages/client/src/Config.ts | 1 + packages/common/src/Config.ts | 23 +++++++++++++++++++ 6 files changed, 47 insertions(+), 5 deletions(-) diff --git a/packages/ali/src/domain/AliFootprintEstimationConstants.ts b/packages/ali/src/domain/AliFootprintEstimationConstants.ts index c9e48e8a4..461f805eb 100644 --- a/packages/ali/src/domain/AliFootprintEstimationConstants.ts +++ b/packages/ali/src/domain/AliFootprintEstimationConstants.ts @@ -148,5 +148,5 @@ export const ALI_CLOUD_CONSTANTS: CloudConstantsByProvider = { SERVER_EXPECTED_LIFESPAN: 35040, // 4 years in hours } -export const AWS_EMISSIONS_FACTORS_METRIC_TON_PER_KWH: CloudConstantsEmissionsFactors = +export const ALI_EMISSIONS_FACTORS_METRIC_TON_PER_KWH: CloudConstantsEmissionsFactors = {} diff --git a/packages/ali/src/domain/index.ts b/packages/ali/src/domain/index.ts index 2fdebb950..e756e9ea6 100644 --- a/packages/ali/src/domain/index.ts +++ b/packages/ali/src/domain/index.ts @@ -2,6 +2,6 @@ * © 2023 Thoughtworks, Inc. */ export { - AWS_EMISSIONS_FACTORS_METRIC_TON_PER_KWH, + ALI_EMISSIONS_FACTORS_METRIC_TON_PER_KWH, ALI_CLOUD_CONSTANTS, } from './AliFootprintEstimationConstants' diff --git a/packages/api/.env.template b/packages/api/.env.template index 2f2ce99e6..acd485665 100644 --- a/packages/api/.env.template +++ b/packages/api/.env.template @@ -66,6 +66,11 @@ AZURE_RESOURCE_TAG_NAMES=["resourceGroup"] # eg. ["resourceGroup","project","cus # To avoid rate limiting, azure estimations can be chunked by days AZURE_CONSUMPTION_CHUNKS_DAYS=0 +# Ali + +ALI_INCLUDE_ESTIMATES=true +ALI_USE_BILLING_DATA=true + # Cache # Optionally set which cache you are using (defaults to local) diff --git a/packages/app/src/App.ts b/packages/app/src/App.ts index ac45dfe02..e79a0b5c9 100644 --- a/packages/app/src/App.ts +++ b/packages/app/src/App.ts @@ -30,6 +30,7 @@ import { OnPremise } from '@cloud-carbon-footprint/on-premise' import cache from './Cache' import { EstimationRequest, RecommendationRequest } from './CreateValidRequest' import { includeCloudProviders } from './common/helpers' +import { ALIAccount } from '@cloud-carbon-footprint/ali' export const recommendationsMockPath = 'recommendations.mock.json' @@ -43,7 +44,7 @@ export default class App { const grouping = request.groupBy as GroupBy const config = configLoader() includeCloudProviders(cloudProviderToSeed, config) - const { AWS, GCP, AZURE } = config + const { AWS, GCP, AZURE, ALI } = config if (process.env.TEST_MODE) { return [] } @@ -113,12 +114,24 @@ export default class App { AzureEstimatesByRegion.push(estimates) appLogger.info('Finished Azure Estimations') } - + const AliEstimatesByRegion: EstimationResult[][] = [] + if (ALI?.INCLUDE_ESTIMATES && ALI?.USE_BILLING_DATA) { + appLogger.info('Starting Ali Estimations') + const aliAccount = new ALIAccount() + const estimates = await aliAccount.getDataFromCostAndUsageReports( + startDate, + endDate, + grouping, + ) + AliEstimatesByRegion.push(estimates) + appLogger.info('Finished Ali Estimations') + } return reduceByTimestamp( AWSEstimatesByRegion.flat() .flat() .concat(GCPEstimatesByRegion.flat()) - .concat(AzureEstimatesByRegion.flat()), + .concat(AzureEstimatesByRegion.flat()) + .concat(AliEstimatesByRegion.flat()), ) } diff --git a/packages/client/src/Config.ts b/packages/client/src/Config.ts index b2cacaaf9..adab56bc2 100644 --- a/packages/client/src/Config.ts +++ b/packages/client/src/Config.ts @@ -46,6 +46,7 @@ const appConfig: ClientConfig = { { key: 'aws', name: 'AWS' }, { key: 'gcp', name: 'GCP' }, { key: 'azure', name: 'Azure' }, + { key: 'ali', name: 'ALI' }, ], PREVIOUS_YEAR_OF_USAGE: previousYearOfUsage, DATE_RANGE: { diff --git a/packages/common/src/Config.ts b/packages/common/src/Config.ts index 30f87febd..83f684dd5 100644 --- a/packages/common/src/Config.ts +++ b/packages/common/src/Config.ts @@ -60,6 +60,13 @@ export interface CCFConfig { RESOURCE_TAG_NAMES?: string[] CONSUMPTION_CHUNKS_DAYS?: number } + ALI?: { + NAME?: string + CURRENT_SERVICES?: { key: string; name: string }[] + CURRENT_REGIONS?: string[] + INCLUDE_ESTIMATES?: boolean + USE_BILLING_DATA?: boolean + } LOGGING_MODE?: string CACHE_MODE?: string ON_PREMISE?: { @@ -264,6 +271,22 @@ const getConfig = (): CCFConfig => ({ getEnvVar('AZURE_CONSUMPTION_CHUNKS_DAYS') || '0', ), }, + ALI: { + NAME: 'ALI', + CURRENT_REGIONS: ['us-east1', 'us-central1', 'us-west1'], + CURRENT_SERVICES: [ + { + key: 'computeEngine', + name: 'ComputeEngine', + }, + ], + INCLUDE_ESTIMATES: process.env.ALI_INCLUDE_ESTIMATES + ? !!process.env.ALI_INCLUDE_ESTIMATES + : true, + USE_BILLING_DATA: + !!process.env.ALI_USE_BILLING_DATA && + process.env.ALI_USE_BILLING_DATA !== 'false', + }, LOGGING_MODE: process.env.LOGGING_MODE || '', CACHE_MODE: getEnvVar('CACHE_MODE') || '', ON_PREMISE: { From d27001613759cf43c5291f54e0199f92b4f1aae2 Mon Sep 17 00:00:00 2001 From: jianglinwang Date: Tue, 14 Mar 2023 13:51:26 +0800 Subject: [PATCH 081/251] add ali mock data and carbon intensity map. --- .../CarbonIntensityMap/CarbonIntensityMap.tsx | 3 +- packages/client/stub-server/mockData.json | 1540 ++++++++++++++++- 2 files changed, 1541 insertions(+), 2 deletions(-) diff --git a/packages/client/src/pages/EmissionsMetricsPage/CarbonIntensityMap/CarbonIntensityMap.tsx b/packages/client/src/pages/EmissionsMetricsPage/CarbonIntensityMap/CarbonIntensityMap.tsx index 7e7a1343c..112a73da8 100644 --- a/packages/client/src/pages/EmissionsMetricsPage/CarbonIntensityMap/CarbonIntensityMap.tsx +++ b/packages/client/src/pages/EmissionsMetricsPage/CarbonIntensityMap/CarbonIntensityMap.tsx @@ -11,7 +11,7 @@ import GCPMap from './GCPMap.png' import AzureMap from './AzureMap.png' import useStyles from './carbonIntensityStyles' -type CloudProvider = 'AWS' | 'GCP' | 'Azure' +type CloudProvider = 'AWS' | 'GCP' | 'Azure' | 'ALI' type IntensityMaps = { [provider in CloudProvider]: React.ReactNode @@ -25,6 +25,7 @@ const CarbonIntensityMap = (): ReactElement => { AWS: AWSMap, GCP: GCPMap, Azure: AzureMap, + ALI: AWSMap, } const handleChange = (event: React.ChangeEvent<{ value: string }>) => { diff --git a/packages/client/stub-server/mockData.json b/packages/client/stub-server/mockData.json index b25b9639e..f24dca3d1 100644 --- a/packages/client/stub-server/mockData.json +++ b/packages/client/stub-server/mockData.json @@ -1 +1,1539 @@ -{"emissions":[{"region":"us-east-1","mtPerKwHour":0.000379069},{"region":"us-east-2","mtPerKwHour":0.000410608},{"region":"us-west-1","mtPerKwHour":0.000322167},{"region":"us-west-2","mtPerKwHour":0.000322167},{"region":"us-gov-east-1","mtPerKwHour":0.000379069},{"region":"us-gov-west-1","mtPerKwHour":0.000322167},{"region":"af-south-1","mtPerKwHour":0.0009006},{"region":"ap-east-1","mtPerKwHour":0.00071},{"region":"ap-south-1","mtPerKwHour":0.0007082},{"region":"ap-northeast-3","mtPerKwHour":0.0004658},{"region":"ap-northeast-2","mtPerKwHour":0.0004156},{"region":"ap-southeast-1","mtPerKwHour":0.000408},{"region":"ap-southeast-2","mtPerKwHour":0.00076},{"region":"ap-northeast-1","mtPerKwHour":0.0004658},{"region":"ca-central-1","mtPerKwHour":0.00012},{"region":"cn-north-1","mtPerKwHour":0.0005374},{"region":"cn-northwest-1","mtPerKwHour":0.0005374},{"region":"eu-central-1","mtPerKwHour":0.000311},{"region":"eu-west-1","mtPerKwHour":0.0002786},{"region":"eu-west-2","mtPerKwHour":0.000225},{"region":"eu-south-1","mtPerKwHour":0.0002134},{"region":"eu-west-3","mtPerKwHour":0.0000511},{"region":"eu-north-1","mtPerKwHour":0.0000088},{"region":"me-south-1","mtPerKwHour":0.0005059},{"region":"sa-east-1","mtPerKwHour":0.0000617},{"region":"us-central1","mtPerKwHour":0.000454},{"region":"us-central2","mtPerKwHour":0.000454},{"region":"us-east1","mtPerKwHour":0.00048},{"region":"us-east4","mtPerKwHour":0.000361},{"region":"us-west1","mtPerKwHour":0.000078},{"region":"us-west2","mtPerKwHour":0.000253},{"region":"us-west3","mtPerKwHour":0.000533},{"region":"us-west4","mtPerKwHour":0.000455},{"region":"asia-east1","mtPerKwHour":0.00054},{"region":"asia-east2","mtPerKwHour":0.000453},{"region":"asia-northeast1","mtPerKwHour":0.000554},{"region":"asia-northeast2","mtPerKwHour":0.000442},{"region":"asia-northeast3","mtPerKwHour":0.000457},{"region":"asia-south1","mtPerKwHour":0.000721},{"region":"asia-southeast1","mtPerKwHour":0.000493},{"region":"asia-southeast2","mtPerKwHour":0.000647},{"region":"australia-southeast1","mtPerKwHour":0.000727},{"region":"europe-north1","mtPerKwHour":0.000133},{"region":"europe-west1","mtPerKwHour":0.000212},{"region":"europe-west2","mtPerKwHour":0.000231},{"region":"europe-west3","mtPerKwHour":0.000293},{"region":"europe-west4","mtPerKwHour":0.00041},{"region":"europe-west6","mtPerKwHour":0.000087},{"region":"northamerica-northeast1","mtPerKwHour":0.000027},{"region":"southamerica-east1","mtPerKwHour":0.000103},{"region":"AP East","mtPerKwHour":0.00071},{"region":"EU West","mtPerKwHour":0.0003284},{"region":"IN Central","mtPerKwHour":0.0007082},{"region":"UK South","mtPerKwHour":0.000225},{"region":"UK West","mtPerKwHour":0.000225},{"region":"US Central","mtPerKwHour":0.000426254},{"region":"US East","mtPerKwHour":0.000379069},{"region":"US South Central","mtPerKwHour":0.000373231},{"region":"US West","mtPerKwHour":0.000322167},{"region":"US West 2","mtPerKwHour":0.000322167},{"region":"Unknown","mtPerKwHour":0.0003852304903666667}],"footprint":[{"timestamp":"2023-05-01T06:00:00.000Z","serviceEstimates":[{"cloudProvider":"AWS","accountName":"aws account 0","serviceName":"ebs","kilowattHours":0.9099318685999922,"co2e":0.00034492696349833045,"cost":2.0611247334106126,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 0","serviceName":"s3","kilowattHours":0.000986946262424086,"co2e":4.0524803092142913e-7,"cost":2.341122015683861,"region":"us-east-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"ec2","kilowattHours":50.176579830111365,"co2e":0.016165238194127487,"cost":2.4154850519158613,"region":"us-west-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"rds","kilowattHours":50.15554852182787,"co2e":0.01615846260063172,"cost":1.8825350541114,"region":"us-west-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 0","serviceName":"lambda","kilowattHours":72.26453941514701,"co2e":0.027393246691560364,"cost":2.0084835420769656,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"GCP","accountName":"gcp account 4","serviceName":"computeEngine","kilowattHours":68.54797801901817,"co2e":0.03290302944912873,"cost":1.8688785461442674,"region":"us-east1","usesAverageCPUConstant":false},{"cloudProvider":"AZURE","accountName":"azure account 4","serviceName":"virtualMachines","kilowattHours":50.02721869884774,"co2e":0.011256124207240741,"cost":2.2910467204304323,"region":"UK South","usesAverageCPUConstant":false}]},{"timestamp":"2023-04-01T06:00:00.000Z","serviceEstimates":[{"cloudProvider":"AWS","accountName":"aws account 0","serviceName":"ebs","kilowattHours":0.9281382786569483,"co2e":0.00035182844915221075,"cost":2.225771432143803,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"s3","kilowattHours":0.00003270772530525257,"co2e":1.3430053672139147e-8,"cost":1.5028221506352941,"region":"us-east-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"ec2","kilowattHours":50.558103056454314,"co2e":0.016288152387388715,"cost":2.440745140900739,"region":"us-west-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"rds","kilowattHours":62.615457571422965,"co2e":0.02017263411941262,"cost":1.5955486508938537,"region":"us-west-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 0","serviceName":"lambda","kilowattHours":50.903421738830794,"co2e":0.01929590917511685,"cost":2.238206567880293,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"GCP","accountName":"gcp account 1","serviceName":"computeEngine","kilowattHours":65.28200479098749,"co2e":0.0050919963736970235,"cost":1.6177172869839958,"region":"us-west1","usesAverageCPUConstant":false},{"cloudProvider":"AZURE","accountName":"azure account 1","serviceName":"virtualMachines","kilowattHours":61.90994721222564,"co2e":0.013929738122750768,"cost":2.2081554041299234,"region":"UK South","usesAverageCPUConstant":false}]},{"timestamp":"2023-03-01T07:00:00.000Z","serviceEstimates":[{"cloudProvider":"AWS","accountName":"aws account 1","serviceName":"ebs","kilowattHours":0.9290466782034781,"co2e":0.0003521727952599142,"cost":1.9170203054445436,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 1","serviceName":"s3","kilowattHours":0.00007414638904524828,"co2e":3.0445100513091304e-8,"cost":2.3496108515083787,"region":"us-east-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"ec2","kilowattHours":50.74719344234976,"co2e":0.016349071069741494,"cost":1.8854738557659787,"region":"us-west-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 4","serviceName":"rds","kilowattHours":62.70792733734538,"co2e":0.02020242482649055,"cost":1.9619661070136278,"region":"us-west-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 2","serviceName":"lambda","kilowattHours":50.18018466997432,"co2e":0.019021752422662495,"cost":1.7366812626585235,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"GCP","accountName":"gcp account 3","serviceName":"computeEngine","kilowattHours":50.642678514609926,"co2e":0.024308485687012764,"cost":2.112304415112801,"region":"us-east1","usesAverageCPUConstant":false},{"cloudProvider":"AZURE","accountName":"azure account 1","serviceName":"virtualMachines","kilowattHours":50.39790599515025,"co2e":0.011339528848908806,"cost":1.844015443986478,"region":"UK South","usesAverageCPUConstant":false}]},{"timestamp":"2023-02-01T07:00:00.000Z","serviceEstimates":[{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"ebs","kilowattHours":0.7903104123827822,"co2e":0.0002995821777115289,"cost":1.5941517912292598,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"s3","kilowattHours":0.0008485340026475967,"co2e":3.484148497591244e-7,"cost":2.422188298668747,"region":"us-east-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 2","serviceName":"ec2","kilowattHours":50.571988266018494,"co2e":0.01629262574369838,"cost":1.813087747561918,"region":"us-west-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"rds","kilowattHours":50.54507810450079,"co2e":0.016283956177692707,"cost":2.3595674179586856,"region":"us-west-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 4","serviceName":"lambda","kilowattHours":50.31390279587026,"co2e":0.019072440818927745,"cost":1.6209696239449094,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"GCP","accountName":"gcp account 2","serviceName":"computeEngine","kilowattHours":50.995343212272175,"co2e":0.01290182183270486,"cost":2.291652099168084,"region":"us-west2","usesAverageCPUConstant":false},{"cloudProvider":"AZURE","accountName":"azure account 1","serviceName":"virtualMachines","kilowattHours":64.71563943290937,"co2e":0.014561018872404607,"cost":2.1519820863003964,"region":"UK South","usesAverageCPUConstant":false}]},{"timestamp":"2023-01-01T07:00:00.000Z","serviceEstimates":[{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"ebs","kilowattHours":0.616242116061845,"co2e":0.00023359828269344752,"cost":1.7611637388166252,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 2","serviceName":"s3","kilowattHours":0.00009916949627876193,"co2e":4.071978852802988e-8,"cost":2.4426180887131026,"region":"us-east-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 2","serviceName":"ec2","kilowattHours":50.149667680040125,"co2e":0.016156567987475487,"cost":2.2645178139778706,"region":"us-west-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 4","serviceName":"rds","kilowattHours":50.15605062459718,"co2e":0.0161586243615746,"cost":2.345203244211236,"region":"us-west-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 2","serviceName":"lambda","kilowattHours":55.97282211541847,"co2e":0.021217561706469563,"cost":1.6084626580032653,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"GCP","accountName":"gcp account 0","serviceName":"computeEngine","kilowattHours":50.58370656686491,"co2e":0.02428017915209516,"cost":2.002632458514051,"region":"us-east1","usesAverageCPUConstant":false},{"cloudProvider":"AZURE","accountName":"azure account 2","serviceName":"virtualMachines","kilowattHours":50.91095442793027,"co2e":0.01145496474628431,"cost":1.9447149842967881,"region":"UK South","usesAverageCPUConstant":false}]},{"timestamp":"2022-12-01T07:00:00.000Z","serviceEstimates":[{"cloudProvider":"AWS","accountName":"aws account 2","serviceName":"ebs","kilowattHours":0.8445236465308483,"co2e":0.00032013273416680216,"cost":1.8371062169192403,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 2","serviceName":"s3","kilowattHours":0.0002648754924918617,"co2e":1.0875999622109836e-7,"cost":2.236483110571217,"region":"us-east-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"ec2","kilowattHours":50.37629287099671,"co2e":0.016229579145370397,"cost":2.449362850757937,"region":"us-west-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"rds","kilowattHours":50.52526081119707,"co2e":0.016277571699760927,"cost":2.302982452488826,"region":"us-west-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"lambda","kilowattHours":50.357072062609866,"co2e":0.01908880494970146,"cost":2.3300950152526614,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"GCP","accountName":"gcp account 0","serviceName":"computeEngine","kilowattHours":53.70289639150512,"co2e":0.02577739026792246,"cost":1.8981217535028845,"region":"us-east1","usesAverageCPUConstant":false},{"cloudProvider":"AZURE","accountName":"azure account 4","serviceName":"virtualMachines","kilowattHours":50.82389577797691,"co2e":0.011435376550044804,"cost":1.5108425566125716,"region":"UK South","usesAverageCPUConstant":false}]},{"timestamp":"2022-11-01T06:00:00.000Z","serviceEstimates":[{"cloudProvider":"AWS","accountName":"aws account 0","serviceName":"ebs","kilowattHours":0.33278898489743725,"co2e":0.00012614998771608664,"cost":2.201012388385645,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 1","serviceName":"s3","kilowattHours":0.00098701927251199,"co2e":4.052780094476032e-7,"cost":1.645103657701107,"region":"us-east-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 4","serviceName":"ec2","kilowattHours":50.904310297531644,"co2e":0.016399688935624875,"cost":2.235818514499144,"region":"us-west-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"rds","kilowattHours":50.12852654254913,"co2e":0.016149757010633425,"cost":1.5786907055484218,"region":"us-west-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 2","serviceName":"lambda","kilowattHours":59.54349975897656,"co2e":0.022571094910135484,"cost":2.324453671310117,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"GCP","accountName":"gcp account 3","serviceName":"computeEngine","kilowattHours":63.69530490695243,"co2e":0.03057374635533717,"cost":2.4104193295098923,"region":"us-east1","usesAverageCPUConstant":false},{"cloudProvider":"AZURE","accountName":"azure account 0","serviceName":"virtualMachines","kilowattHours":65.60925208439582,"co2e":0.014762081718989059,"cost":2.4070137425368685,"region":"UK South","usesAverageCPUConstant":false}]},{"timestamp":"2022-10-01T06:00:00.000Z","serviceEstimates":[{"cloudProvider":"AWS","accountName":"aws account 4","serviceName":"ebs","kilowattHours":0.7421818551593697,"co2e":0.00028133813365340714,"cost":2.498136748374863,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 1","serviceName":"s3","kilowattHours":0.0002180651158962681,"co2e":8.953928110793486e-8,"cost":1.7632736153406632,"region":"us-east-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 0","serviceName":"ec2","kilowattHours":54.239336047864754,"co2e":0.017474124176532442,"cost":1.9661288302344184,"region":"us-west-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 0","serviceName":"rds","kilowattHours":50.23120235816861,"co2e":0.016182835770124106,"cost":2.0142111162898986,"region":"us-west-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"lambda","kilowattHours":50.86615400739505,"co2e":0.019281782133429234,"cost":2.164622206937599,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"GCP","accountName":"gcp account 3","serviceName":"computeEngine","kilowattHours":59.19683162717123,"co2e":0.02841447918104219,"cost":1.6906306697783389,"region":"us-east1","usesAverageCPUConstant":false},{"cloudProvider":"AZURE","accountName":"azure account 2","serviceName":"virtualMachines","kilowattHours":50.27713613081028,"co2e":0.011312355629432311,"cost":1.7863670364270168,"region":"UK South","usesAverageCPUConstant":false}]},{"timestamp":"2022-09-01T06:00:00.000Z","serviceEstimates":[{"cloudProvider":"AWS","accountName":"aws account 0","serviceName":"ebs","kilowattHours":0.5648293345133781,"co2e":0.00021410929100465172,"cost":2.3304265345916653,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 4","serviceName":"s3","kilowattHours":0.00005263625919629122,"co2e":2.161286911607075e-8,"cost":1.6038362843689178,"region":"us-east-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 0","serviceName":"ec2","kilowattHours":64.1169430041115,"co2e":0.02065636317680559,"cost":1.8957903016901754,"region":"us-west-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 1","serviceName":"rds","kilowattHours":50.69439982629539,"co2e":0.016332062708838108,"cost":2.3649041975938676,"region":"us-west-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 4","serviceName":"lambda","kilowattHours":50.69873776324742,"co2e":0.01921831982517644,"cost":2.1212012771669846,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"GCP","accountName":"gcp account 2","serviceName":"computeEngine","kilowattHours":50.71620115791394,"co2e":0.024343776555798693,"cost":2.0052165772682793,"region":"us-east1","usesAverageCPUConstant":false},{"cloudProvider":"AZURE","accountName":"azure account 0","serviceName":"virtualMachines","kilowattHours":50.146548832423804,"co2e":0.011282973487295355,"cost":2.4727258099898037,"region":"UK South","usesAverageCPUConstant":false}]},{"timestamp":"2022-08-01T06:00:00.000Z","serviceEstimates":[{"cloudProvider":"AWS","accountName":"aws account 0","serviceName":"ebs","kilowattHours":0.35269028620932774,"co2e":0.00013369395410308364,"cost":2.426536030208615,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 2","serviceName":"s3","kilowattHours":0.00007772936246220752,"co2e":3.1916298061882105e-8,"cost":1.9926068310477163,"region":"us-east-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 4","serviceName":"ec2","kilowattHours":50.00593572462313,"co2e":0.016110262294594658,"cost":1.9838511921282318,"region":"us-west-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"rds","kilowattHours":50.803461347266776,"co2e":0.016367198731864895,"cost":2.229003808663734,"region":"us-west-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 0","serviceName":"lambda","kilowattHours":52.62976125953012,"co2e":0.019950310970888823,"cost":1.6972395592555778,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"GCP","accountName":"gcp account 4","serviceName":"computeEngine","kilowattHours":64.63052755166217,"co2e":0.03102265322479784,"cost":2.30459191166866,"region":"us-east1","usesAverageCPUConstant":false},{"cloudProvider":"AZURE","accountName":"azure account 1","serviceName":"virtualMachines","kilowattHours":53.31425378242701,"co2e":0.011995707101046077,"cost":1.6930870858502807,"region":"UK South","usesAverageCPUConstant":false}]},{"timestamp":"2022-07-01T06:00:00.000Z","serviceEstimates":[{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"ebs","kilowattHours":0.3793961762889928,"co2e":0.00014381732914969223,"cost":1.5660349090236223,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 2","serviceName":"s3","kilowattHours":0.0007881261941471649,"co2e":3.2361092032637913e-7,"cost":2.107104300712024,"region":"us-east-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"ec2","kilowattHours":50.4099085739953,"co2e":0.016240409015558344,"cost":1.7912671435326524,"region":"us-west-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 1","serviceName":"rds","kilowattHours":56.61135475563019,"co2e":0.01823831032755711,"cost":2.0547082075494254,"region":"us-west-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 4","serviceName":"lambda","kilowattHours":50.16319007556745,"co2e":0.019015310298755278,"cost":2.3442440999125886,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"GCP","accountName":"gcp account 1","serviceName":"computeEngine","kilowattHours":50.2235650164992,"co2e":0.02410731120791962,"cost":1.6300863324887378,"region":"us-east1","usesAverageCPUConstant":false},{"cloudProvider":"AZURE","accountName":"azure account 3","serviceName":"virtualMachines","kilowattHours":50.27485053251475,"co2e":0.011311841369815818,"cost":1.7666414199697307,"region":"UK South","usesAverageCPUConstant":false}]},{"timestamp":"2022-06-01T06:00:00.000Z","serviceEstimates":[{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"ebs","kilowattHours":0.0025963667867079376,"co2e":9.842021614705911e-7,"cost":2.110850189120251,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"s3","kilowattHours":0.00038988295545011175,"co2e":1.600890605714595e-7,"cost":1.750590801038299,"region":"us-east-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 4","serviceName":"ec2","kilowattHours":50.58419719553395,"co2e":0.016296559057893588,"cost":2.331961666410219,"region":"us-west-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 1","serviceName":"rds","kilowattHours":50.09219672002729,"co2e":0.01613805274070103,"cost":2.171231385841728,"region":"us-west-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 0","serviceName":"lambda","kilowattHours":50.34788323768369,"co2e":0.01908532175102552,"cost":2.178860527900543,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"GCP","accountName":"gcp account 2","serviceName":"computeEngine","kilowattHours":50.876621782936894,"co2e":0.02442077845580971,"cost":2.008009566294815,"region":"us-east1","usesAverageCPUConstant":false},{"cloudProvider":"AZURE","accountName":"azure account 2","serviceName":"virtualMachines","kilowattHours":69.14280006058016,"co2e":0.015557130013630535,"cost":2.159049075087805,"region":"UK South","usesAverageCPUConstant":false}]},{"timestamp":"2022-05-01T06:00:00.000Z","serviceEstimates":[{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"ebs","kilowattHours":0.847821319053617,"co2e":0.00032138277959233556,"cost":1.8756121359502171,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"s3","kilowattHours":0.000630093974723783,"co2e":2.587216267733831e-7,"cost":2.3698572941873914,"region":"us-east-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"ec2","kilowattHours":50.10307382046631,"co2e":0.016141556983518168,"cost":2.351022985532233,"region":"us-west-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 2","serviceName":"rds","kilowattHours":50.22293041559674,"co2e":0.016180170823201556,"cost":1.5327957463127884,"region":"us-west-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"lambda","kilowattHours":68.18586584927158,"co2e":0.025847147981617528,"cost":2.205957045144824,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"GCP","accountName":"gcp account 4","serviceName":"computeEngine","kilowattHours":68.47759811709409,"co2e":0.032869247096205166,"cost":2.026647361534485,"region":"us-east1","usesAverageCPUConstant":false},{"cloudProvider":"AZURE","accountName":"azure account 1","serviceName":"virtualMachines","kilowattHours":50.5125618456214,"co2e":0.011365326415264814,"cost":1.9833785822656285,"region":"UK South","usesAverageCPUConstant":false}]},{"timestamp":"2022-04-01T06:00:00.000Z","serviceEstimates":[{"cloudProvider":"AWS","accountName":"aws account 1","serviceName":"ebs","kilowattHours":0.9119831149849784,"co2e":0.00034570452741424076,"cost":1.7466850895849164,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 1","serviceName":"s3","kilowattHours":0.00010168764085273696,"co2e":4.175375883526062e-8,"cost":2.0929755865584747,"region":"us-east-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 2","serviceName":"ec2","kilowattHours":50.13033413155082,"co2e":0.01615033935615933,"cost":2.030781896188273,"region":"us-west-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"rds","kilowattHours":53.81264913939785,"co2e":0.017336659735292387,"cost":1.7863504430322306,"region":"us-west-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 4","serviceName":"lambda","kilowattHours":63.45075892152083,"co2e":0.02405221573362198,"cost":1.8740007850011755,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"GCP","accountName":"gcp account 4","serviceName":"computeEngine","kilowattHours":50.77403726191187,"co2e":0.0243715378857177,"cost":1.7839515021969587,"region":"us-east1","usesAverageCPUConstant":false},{"cloudProvider":"AZURE","accountName":"azure account 4","serviceName":"virtualMachines","kilowattHours":50.373127573933836,"co2e":0.011333953704135112,"cost":1.8199290662411896,"region":"UK South","usesAverageCPUConstant":false}]},{"timestamp":"2022-03-01T07:00:00.000Z","serviceEstimates":[{"cloudProvider":"AWS","accountName":"aws account 1","serviceName":"ebs","kilowattHours":0.8148394263939371,"co2e":0.00030888036652372336,"cost":1.9952062784974551,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 1","serviceName":"s3","kilowattHours":0.00010100550904187954,"co2e":4.147367005666808e-8,"cost":2.479801470978538,"region":"us-east-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 1","serviceName":"ec2","kilowattHours":50.933103987612505,"co2e":0.016408965312377156,"cost":2.014818576889102,"region":"us-west-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 2","serviceName":"rds","kilowattHours":50.92736661108625,"co2e":0.016407116918993825,"cost":2.0552869714092585,"region":"us-west-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 2","serviceName":"lambda","kilowattHours":70.47173348463286,"co2e":0.026713649540286294,"cost":1.8418849726705238,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"GCP","accountName":"gcp account 3","serviceName":"computeEngine","kilowattHours":57.666701915218034,"co2e":0.02768001691930466,"cost":1.611910606014586,"region":"us-east1","usesAverageCPUConstant":false},{"cloudProvider":"AZURE","accountName":"azure account 2","serviceName":"virtualMachines","kilowattHours":50.773157853289746,"co2e":0.011423960516990192,"cost":2.2728625664822433,"region":"UK South","usesAverageCPUConstant":false}]}],"recommendations":[{"cloudProvider":"AWS","accountId":"aws account 0","accountName":"aws account 0","region":"us-west-1","recommendationType":"Modify","instanceName":"example-instance-5","recommendationDetail":"Modify instance: example-instance-5.","resourceId":"i-0f12345678912b12I","kilowattHourSavings":116.513,"costSavings":3.611,"co2eSavings":0.037536643671},{"cloudProvider":"AWS","accountId":"aws account 1","accountName":"aws account 1","region":"us-east-2","recommendationType":"Modify","instanceName":"example-instance","recommendationDetail":"Modify instance: example-instance.","resourceId":"i-0f12345678912b12I","kilowattHourSavings":114.978,"costSavings":13.506,"co2eSavings":0.047210886624},{"cloudProvider":"AWS","accountId":"aws account 2","accountName":"aws account 2","region":"us-east-1","recommendationType":"Modify","instanceName":"example-instance-2","recommendationDetail":"Modify instance: example-instance-2.","resourceId":"i-0f12345678912b12I","kilowattHourSavings":18.419,"costSavings":5.667,"co2eSavings":0.0069820719110000005},{"cloudProvider":"AWS","accountId":"aws account 3","accountName":"aws account 3","region":"us-west-2","recommendationType":"Delete","instanceName":"example-instance","recommendationDetail":"Delete instance: example-instance.","resourceId":"i-0f12345678912b12I","kilowattHourSavings":11.195,"costSavings":4.442,"co2eSavings":0.003606659565},{"cloudProvider":"AWS","accountId":"aws account 4","accountName":"aws account 4","region":"us-west-1","recommendationType":"Delete","instanceName":"example-instance","recommendationDetail":"Delete instance: example-instance.","resourceId":"i-0f12345678912b12I","kilowattHourSavings":111.717,"costSavings":5.788,"co2eSavings":0.035991530739},{"cloudProvider":"GCP","accountId":"gcp account 0","accountName":"gcp account 0","region":"us-west1","recommendationType":"DELETE_IMAGE","instanceName":"test-instance-1","recommendationDetail":"Save cost by performing a DELETE_IMAGE for instance: test-instance-1.","resourceId":6906976106869124000,"kilowattHourSavings":12.081,"costSavings":1.314,"co2eSavings":0.0009423179999999999},{"cloudProvider":"GCP","accountId":"gcp account 1","accountName":"gcp account 1","region":"us-west1","recommendationType":"SNAPSHOT_AND_DELETE_DISK","instanceName":"test-instance-3","recommendationDetail":"Save cost by performing a SNAPSHOT_AND_DELETE_DISK for instance: test-instance-3.","resourceId":4256745502855943000,"kilowattHourSavings":18.742,"costSavings":4.549,"co2eSavings":0.001461876},{"cloudProvider":"GCP","accountId":"gcp account 2","accountName":"gcp account 2","region":"us-west1","recommendationType":"CHANGE_MACHINE_TYPE","instanceName":"test-instance-10","recommendationDetail":"Save cost by performing a CHANGE_MACHINE_TYPE for instance: test-instance-10.","resourceId":9625521363699055000,"kilowattHourSavings":16.989,"costSavings":8.165,"co2eSavings":0.001325142},{"cloudProvider":"GCP","accountId":"gcp account 3","accountName":"gcp account 3","region":"us-east1","recommendationType":"DELETE_ADDRESS","instanceName":"test-instance-8","recommendationDetail":"Save cost by performing a DELETE_ADDRESS for instance: test-instance-8.","resourceId":9351508929180877000,"kilowattHourSavings":19.946,"costSavings":5.2,"co2eSavings":0.00957408},{"cloudProvider":"GCP","accountId":"gcp account 4","accountName":"gcp account 4","region":"us-west2","recommendationType":"DELETE_DISK","instanceName":"test-instance-14","recommendationDetail":"Save cost by performing a DELETE_DISK for instance: test-instance-14.","resourceId":7840914330904416000,"kilowattHourSavings":18.782,"costSavings":8.359,"co2eSavings":0.0047518460000000005},{"cloudProvider":"GCP","accountId":"gcp account 2","accountName":"gcp account 2","region":"us-east1","recommendationType":"STOP_VM","instanceName":"test-instance-9","recommendationDetail":"Save cost by performing a STOP_VM for instance: test-instance-9.","resourceId":8928403120086348000,"kilowattHourSavings":116.483,"costSavings":8.409,"co2eSavings":0.055911840000000004},{"cloudProvider":"GCP","accountId":"gcp account 4","accountName":"gcp account 4","region":"us-east1","recommendationType":"STOP_VM","instanceName":"test-instance-9","recommendationDetail":"Save cost by performing a STOP_VM for instance: test-instance-9.","resourceId":8928403120086348000,"kilowattHourSavings":0.0001,"costSavings":0.0002,"co2eSavings":4.8000000000000006e-8},{"cloudProvider":"GCP","accountId":"gcp account 4","accountName":"gcp account 4","region":"us-east1","recommendationType":"STOP_VM","instanceName":"test-instance-9","recommendationDetail":"Save cost by performing a STOP_VM for instance: test-instance-9.","resourceId":8928403120086348000,"kilowattHourSavings":0,"costSavings":0,"co2eSavings":0},{"cloudProvider":"GCP","accountId":"gcp account 4","accountName":"gcp account 4","region":"us-east1","recommendationType":"STOP_VM","instanceName":"test-instance-9","recommendationDetail":"Save cost by performing a STOP_VM for instance: test-instance-9.","resourceId":8928403120086348000,"kilowattHourSavings":0.001,"costSavings":0.001,"co2eSavings":4.800000000000001e-7},{"cloudProvider":"GCP","accountId":"gcp account 4","accountName":"gcp account 4","region":"us-east1","recommendationType":"STOP_VM","instanceName":"test-instance-9","recommendationDetail":"Save cost by performing a STOP_VM for instance: test-instance-9.","resourceId":8928403120086348000,"kilowattHourSavings":0.002,"costSavings":0.002,"co2eSavings":9.600000000000001e-7}]} \ No newline at end of file +{ + "emissions": [ + { "region": "us-east-1", "mtPerKwHour": 0.000379069 }, + { "region": "us-east-2", "mtPerKwHour": 0.000410608 }, + { "region": "us-west-1", "mtPerKwHour": 0.000322167 }, + { "region": "us-west-2", "mtPerKwHour": 0.000322167 }, + { "region": "us-gov-east-1", "mtPerKwHour": 0.000379069 }, + { "region": "us-gov-west-1", "mtPerKwHour": 0.000322167 }, + { "region": "af-south-1", "mtPerKwHour": 0.0009006 }, + { "region": "ap-east-1", "mtPerKwHour": 0.00071 }, + { "region": "ap-south-1", "mtPerKwHour": 0.0007082 }, + { "region": "ap-northeast-3", "mtPerKwHour": 0.0004658 }, + { "region": "ap-northeast-2", "mtPerKwHour": 0.0004156 }, + { "region": "ap-southeast-1", "mtPerKwHour": 0.000408 }, + { "region": "ap-southeast-2", "mtPerKwHour": 0.00076 }, + { "region": "ap-northeast-1", "mtPerKwHour": 0.0004658 }, + { "region": "ca-central-1", "mtPerKwHour": 0.00012 }, + { "region": "cn-north-1", "mtPerKwHour": 0.0005374 }, + { "region": "cn-northwest-1", "mtPerKwHour": 0.0005374 }, + { "region": "eu-central-1", "mtPerKwHour": 0.000311 }, + { "region": "eu-west-1", "mtPerKwHour": 0.0002786 }, + { "region": "eu-west-2", "mtPerKwHour": 0.000225 }, + { "region": "eu-south-1", "mtPerKwHour": 0.0002134 }, + { "region": "eu-west-3", "mtPerKwHour": 0.0000511 }, + { "region": "eu-north-1", "mtPerKwHour": 0.0000088 }, + { "region": "me-south-1", "mtPerKwHour": 0.0005059 }, + { "region": "sa-east-1", "mtPerKwHour": 0.0000617 }, + { "region": "us-central1", "mtPerKwHour": 0.000454 }, + { "region": "us-central2", "mtPerKwHour": 0.000454 }, + { "region": "us-east1", "mtPerKwHour": 0.00048 }, + { "region": "us-east4", "mtPerKwHour": 0.000361 }, + { "region": "us-west1", "mtPerKwHour": 0.000078 }, + { "region": "us-west2", "mtPerKwHour": 0.000253 }, + { "region": "us-west3", "mtPerKwHour": 0.000533 }, + { "region": "us-west4", "mtPerKwHour": 0.000455 }, + { "region": "asia-east1", "mtPerKwHour": 0.00054 }, + { "region": "asia-east2", "mtPerKwHour": 0.000453 }, + { "region": "asia-northeast1", "mtPerKwHour": 0.000554 }, + { "region": "asia-northeast2", "mtPerKwHour": 0.000442 }, + { "region": "asia-northeast3", "mtPerKwHour": 0.000457 }, + { "region": "asia-south1", "mtPerKwHour": 0.000721 }, + { "region": "asia-southeast1", "mtPerKwHour": 0.000493 }, + { "region": "asia-southeast2", "mtPerKwHour": 0.000647 }, + { "region": "australia-southeast1", "mtPerKwHour": 0.000727 }, + { "region": "europe-north1", "mtPerKwHour": 0.000133 }, + { "region": "europe-west1", "mtPerKwHour": 0.000212 }, + { "region": "europe-west2", "mtPerKwHour": 0.000231 }, + { "region": "europe-west3", "mtPerKwHour": 0.000293 }, + { "region": "europe-west4", "mtPerKwHour": 0.00041 }, + { "region": "europe-west6", "mtPerKwHour": 0.000087 }, + { "region": "northamerica-northeast1", "mtPerKwHour": 0.000027 }, + { "region": "southamerica-east1", "mtPerKwHour": 0.000103 }, + { "region": "AP East", "mtPerKwHour": 0.00071 }, + { "region": "EU West", "mtPerKwHour": 0.0003284 }, + { "region": "IN Central", "mtPerKwHour": 0.0007082 }, + { "region": "UK South", "mtPerKwHour": 0.000225 }, + { "region": "UK West", "mtPerKwHour": 0.000225 }, + { "region": "US Central", "mtPerKwHour": 0.000426254 }, + { "region": "US East", "mtPerKwHour": 0.000379069 }, + { "region": "US South Central", "mtPerKwHour": 0.000373231 }, + { "region": "US West", "mtPerKwHour": 0.000322167 }, + { "region": "US West 2", "mtPerKwHour": 0.000322167 }, + { "region": "Unknown", "mtPerKwHour": 0.0003852304903666667 } + ], + "footprint": [ + { + "timestamp": "2023-05-01T06:00:00.000Z", + "serviceEstimates": [ + { + "cloudProvider": "AWS", + "accountName": "aws account 0", + "serviceName": "ebs", + "kilowattHours": 0.9099318685999922, + "co2e": 0.00034492696349833045, + "cost": 2.0611247334106126, + "region": "us-east-1", + "usesAverageCPUConstant": false + }, + { + "cloudProvider": "AWS", + "accountName": "aws account 0", + "serviceName": "s3", + "kilowattHours": 0.000986946262424086, + "co2e": 4.0524803092142913e-7, + "cost": 2.341122015683861, + "region": "us-east-2", + "usesAverageCPUConstant": false + }, + { + "cloudProvider": "AWS", + "accountName": "aws account 3", + "serviceName": "ec2", + "kilowattHours": 50.176579830111365, + "co2e": 0.016165238194127487, + "cost": 2.4154850519158613, + "region": "us-west-1", + "usesAverageCPUConstant": false + }, + { + "cloudProvider": "AWS", + "accountName": "aws account 3", + "serviceName": "rds", + "kilowattHours": 50.15554852182787, + "co2e": 0.01615846260063172, + "cost": 1.8825350541114, + "region": "us-west-2", + "usesAverageCPUConstant": false + }, + { + "cloudProvider": "AWS", + "accountName": "aws account 0", + "serviceName": "lambda", + "kilowattHours": 72.26453941514701, + "co2e": 0.027393246691560364, + "cost": 2.0084835420769656, + "region": "us-east-1", + "usesAverageCPUConstant": false + }, + { + "cloudProvider": "GCP", + "accountName": "gcp account 4", + "serviceName": "computeEngine", + "kilowattHours": 68.54797801901817, + "co2e": 0.03290302944912873, + "cost": 1.8688785461442674, + "region": "us-east1", + "usesAverageCPUConstant": false + }, + { + "cloudProvider": "AZURE", + "accountName": "azure account 4", + "serviceName": "virtualMachines", + "kilowattHours": 50.02721869884774, + "co2e": 0.011256124207240741, + "cost": 2.2910467204304323, + "region": "UK South", + "usesAverageCPUConstant": false + }, + { + "cloudProvider": "AliCloud", + "accountName": "ali account 1", + "serviceName": "virtualMachines", + "kilowattHours": 50.05357763407657, + "co2e": 0.012024032067235646, + "cost": 2.674481284318876, + "region": "cn-hangzhou", + "usesAverageCPUConstant": false + } + ] + }, + { + "timestamp": "2023-04-01T06:00:00.000Z", + "serviceEstimates": [ + { + "cloudProvider": "AWS", + "accountName": "aws account 0", + "serviceName": "ebs", + "kilowattHours": 0.9281382786569483, + "co2e": 0.00035182844915221075, + "cost": 2.225771432143803, + "region": "us-east-1", + "usesAverageCPUConstant": false + }, + { + "cloudProvider": "AWS", + "accountName": "aws account 3", + "serviceName": "s3", + "kilowattHours": 0.00003270772530525257, + "co2e": 1.3430053672139147e-8, + "cost": 1.5028221506352941, + "region": "us-east-2", + "usesAverageCPUConstant": false + }, + { + "cloudProvider": "AWS", + "accountName": "aws account 3", + "serviceName": "ec2", + "kilowattHours": 50.558103056454314, + "co2e": 0.016288152387388715, + "cost": 2.440745140900739, + "region": "us-west-1", + "usesAverageCPUConstant": false + }, + { + "cloudProvider": "AWS", + "accountName": "aws account 3", + "serviceName": "rds", + "kilowattHours": 62.615457571422965, + "co2e": 0.02017263411941262, + "cost": 1.5955486508938537, + "region": "us-west-2", + "usesAverageCPUConstant": false + }, + { + "cloudProvider": "AWS", + "accountName": "aws account 0", + "serviceName": "lambda", + "kilowattHours": 50.903421738830794, + "co2e": 0.01929590917511685, + "cost": 2.238206567880293, + "region": "us-east-1", + "usesAverageCPUConstant": false + }, + { + "cloudProvider": "GCP", + "accountName": "gcp account 1", + "serviceName": "computeEngine", + "kilowattHours": 65.28200479098749, + "co2e": 0.0050919963736970235, + "cost": 1.6177172869839958, + "region": "us-west1", + "usesAverageCPUConstant": false + }, + { + "cloudProvider": "AZURE", + "accountName": "azure account 1", + "serviceName": "virtualMachines", + "kilowattHours": 61.90994721222564, + "co2e": 0.013929738122750768, + "cost": 2.2081554041299234, + "region": "UK South", + "usesAverageCPUConstant": false + }, + { + "cloudProvider": "AliCloud", + "accountName": "ali account 1", + "serviceName": "virtualMachines", + "kilowattHours": 61.05357763407657, + "co2e": 0.013024032067235646, + "cost": 2.274481284318876, + "region": "cn-hangzhou", + "usesAverageCPUConstant": false + } + ] + }, + { + "timestamp": "2023-03-01T07:00:00.000Z", + "serviceEstimates": [ + { + "cloudProvider": "AWS", + "accountName": "aws account 1", + "serviceName": "ebs", + "kilowattHours": 0.9290466782034781, + "co2e": 0.0003521727952599142, + "cost": 1.9170203054445436, + "region": "us-east-1", + "usesAverageCPUConstant": false + }, + { + "cloudProvider": "AWS", + "accountName": "aws account 1", + "serviceName": "s3", + "kilowattHours": 0.00007414638904524828, + "co2e": 3.0445100513091304e-8, + "cost": 2.3496108515083787, + "region": "us-east-2", + "usesAverageCPUConstant": false + }, + { + "cloudProvider": "AWS", + "accountName": "aws account 3", + "serviceName": "ec2", + "kilowattHours": 50.74719344234976, + "co2e": 0.016349071069741494, + "cost": 1.8854738557659787, + "region": "us-west-1", + "usesAverageCPUConstant": false + }, + { + "cloudProvider": "AWS", + "accountName": "aws account 4", + "serviceName": "rds", + "kilowattHours": 62.70792733734538, + "co2e": 0.02020242482649055, + "cost": 1.9619661070136278, + "region": "us-west-2", + "usesAverageCPUConstant": false + }, + { + "cloudProvider": "AWS", + "accountName": "aws account 2", + "serviceName": "lambda", + "kilowattHours": 50.18018466997432, + "co2e": 0.019021752422662495, + "cost": 1.7366812626585235, + "region": "us-east-1", + "usesAverageCPUConstant": false + }, + { + "cloudProvider": "GCP", + "accountName": "gcp account 3", + "serviceName": "computeEngine", + "kilowattHours": 50.642678514609926, + "co2e": 0.024308485687012764, + "cost": 2.112304415112801, + "region": "us-east1", + "usesAverageCPUConstant": false + }, + { + "cloudProvider": "AZURE", + "accountName": "azure account 1", + "serviceName": "virtualMachines", + "kilowattHours": 50.39790599515025, + "co2e": 0.011339528848908806, + "cost": 1.844015443986478, + "region": "UK South", + "usesAverageCPUConstant": false + }, + { + "cloudProvider": "AliCloud", + "accountName": "ali account 1", + "serviceName": "virtualMachines", + "kilowattHours": 50.05357763407657, + "co2e": 0.012024032067235646, + "cost": 2.474481284318876, + "region": "cn-hangzhou", + "usesAverageCPUConstant": false + } + ] + }, + { + "timestamp": "2023-02-01T07:00:00.000Z", + "serviceEstimates": [ + { + "cloudProvider": "AWS", + "accountName": "aws account 3", + "serviceName": "ebs", + "kilowattHours": 0.7903104123827822, + "co2e": 0.0002995821777115289, + "cost": 1.5941517912292598, + "region": "us-east-1", + "usesAverageCPUConstant": false + }, + { + "cloudProvider": "AWS", + "accountName": "aws account 3", + "serviceName": "s3", + "kilowattHours": 0.0008485340026475967, + "co2e": 3.484148497591244e-7, + "cost": 2.422188298668747, + "region": "us-east-2", + "usesAverageCPUConstant": false + }, + { + "cloudProvider": "AWS", + "accountName": "aws account 2", + "serviceName": "ec2", + "kilowattHours": 50.571988266018494, + "co2e": 0.01629262574369838, + "cost": 1.813087747561918, + "region": "us-west-1", + "usesAverageCPUConstant": false + }, + { + "cloudProvider": "AWS", + "accountName": "aws account 3", + "serviceName": "rds", + "kilowattHours": 50.54507810450079, + "co2e": 0.016283956177692707, + "cost": 2.3595674179586856, + "region": "us-west-2", + "usesAverageCPUConstant": false + }, + { + "cloudProvider": "AWS", + "accountName": "aws account 4", + "serviceName": "lambda", + "kilowattHours": 50.31390279587026, + "co2e": 0.019072440818927745, + "cost": 1.6209696239449094, + "region": "us-east-1", + "usesAverageCPUConstant": false + }, + { + "cloudProvider": "GCP", + "accountName": "gcp account 2", + "serviceName": "computeEngine", + "kilowattHours": 50.995343212272175, + "co2e": 0.01290182183270486, + "cost": 2.291652099168084, + "region": "us-west2", + "usesAverageCPUConstant": false + }, + { + "cloudProvider": "AZURE", + "accountName": "azure account 1", + "serviceName": "virtualMachines", + "kilowattHours": 64.71563943290937, + "co2e": 0.014561018872404607, + "cost": 2.1519820863003964, + "region": "UK South", + "usesAverageCPUConstant": false + }, + { + "cloudProvider": "AliCloud", + "accountName": "ali account 1", + "serviceName": "virtualMachines", + "kilowattHours": 65.05357763407657, + "co2e": 0.014024032067235646, + "cost": 2.874481284318876, + "region": "cn-hangzhou", + "usesAverageCPUConstant": false + } + ] + }, + { + "timestamp": "2023-01-01T07:00:00.000Z", + "serviceEstimates": [ + { + "cloudProvider": "AWS", + "accountName": "aws account 3", + "serviceName": "ebs", + "kilowattHours": 0.616242116061845, + "co2e": 0.00023359828269344752, + "cost": 1.7611637388166252, + "region": "us-east-1", + "usesAverageCPUConstant": false + }, + { + "cloudProvider": "AWS", + "accountName": "aws account 2", + "serviceName": "s3", + "kilowattHours": 0.00009916949627876193, + "co2e": 4.071978852802988e-8, + "cost": 2.4426180887131026, + "region": "us-east-2", + "usesAverageCPUConstant": false + }, + { + "cloudProvider": "AWS", + "accountName": "aws account 2", + "serviceName": "ec2", + "kilowattHours": 50.149667680040125, + "co2e": 0.016156567987475487, + "cost": 2.2645178139778706, + "region": "us-west-1", + "usesAverageCPUConstant": false + }, + { + "cloudProvider": "AWS", + "accountName": "aws account 4", + "serviceName": "rds", + "kilowattHours": 50.15605062459718, + "co2e": 0.0161586243615746, + "cost": 2.345203244211236, + "region": "us-west-2", + "usesAverageCPUConstant": false + }, + { + "cloudProvider": "AWS", + "accountName": "aws account 2", + "serviceName": "lambda", + "kilowattHours": 55.97282211541847, + "co2e": 0.021217561706469563, + "cost": 1.6084626580032653, + "region": "us-east-1", + "usesAverageCPUConstant": false + }, + { + "cloudProvider": "GCP", + "accountName": "gcp account 0", + "serviceName": "computeEngine", + "kilowattHours": 50.58370656686491, + "co2e": 0.02428017915209516, + "cost": 2.002632458514051, + "region": "us-east1", + "usesAverageCPUConstant": false + }, + { + "cloudProvider": "AZURE", + "accountName": "azure account 2", + "serviceName": "virtualMachines", + "kilowattHours": 50.91095442793027, + "co2e": 0.01145496474628431, + "cost": 1.9447149842967881, + "region": "UK South", + "usesAverageCPUConstant": false + }, + { + "cloudProvider": "AliCloud", + "accountName": "ali account 1", + "serviceName": "virtualMachines", + "kilowattHours": 70.05357763407657, + "co2e": 0.015024032067235646, + "cost": 2.974481284318876, + "region": "cn-hangzhou", + "usesAverageCPUConstant": false + } + ] + }, + { + "timestamp": "2022-12-01T07:00:00.000Z", + "serviceEstimates": [ + { + "cloudProvider": "AWS", + "accountName": "aws account 2", + "serviceName": "ebs", + "kilowattHours": 0.8445236465308483, + "co2e": 0.00032013273416680216, + "cost": 1.8371062169192403, + "region": "us-east-1", + "usesAverageCPUConstant": false + }, + { + "cloudProvider": "AWS", + "accountName": "aws account 2", + "serviceName": "s3", + "kilowattHours": 0.0002648754924918617, + "co2e": 1.0875999622109836e-7, + "cost": 2.236483110571217, + "region": "us-east-2", + "usesAverageCPUConstant": false + }, + { + "cloudProvider": "AWS", + "accountName": "aws account 3", + "serviceName": "ec2", + "kilowattHours": 50.37629287099671, + "co2e": 0.016229579145370397, + "cost": 2.449362850757937, + "region": "us-west-1", + "usesAverageCPUConstant": false + }, + { + "cloudProvider": "AWS", + "accountName": "aws account 3", + "serviceName": "rds", + "kilowattHours": 50.52526081119707, + "co2e": 0.016277571699760927, + "cost": 2.302982452488826, + "region": "us-west-2", + "usesAverageCPUConstant": false + }, + { + "cloudProvider": "AWS", + "accountName": "aws account 3", + "serviceName": "lambda", + "kilowattHours": 50.357072062609866, + "co2e": 0.01908880494970146, + "cost": 2.3300950152526614, + "region": "us-east-1", + "usesAverageCPUConstant": false + }, + { + "cloudProvider": "GCP", + "accountName": "gcp account 0", + "serviceName": "computeEngine", + "kilowattHours": 53.70289639150512, + "co2e": 0.02577739026792246, + "cost": 1.8981217535028845, + "region": "us-east1", + "usesAverageCPUConstant": false + }, + { + "cloudProvider": "AZURE", + "accountName": "azure account 4", + "serviceName": "virtualMachines", + "kilowattHours": 50.82389577797691, + "co2e": 0.011435376550044804, + "cost": 1.5108425566125716, + "region": "UK South", + "usesAverageCPUConstant": false + }, + { + "cloudProvider": "AliCloud", + "accountName": "ali account 1", + "serviceName": "virtualMachines", + "kilowattHours": 72.05357763407657, + "co2e": 0.015524032067235646, + "cost": 2.874481284318876, + "region": "cn-hangzhou", + "usesAverageCPUConstant": false + } + ] + }, + { + "timestamp": "2022-11-01T06:00:00.000Z", + "serviceEstimates": [ + { + "cloudProvider": "AWS", + "accountName": "aws account 0", + "serviceName": "ebs", + "kilowattHours": 0.33278898489743725, + "co2e": 0.00012614998771608664, + "cost": 2.201012388385645, + "region": "us-east-1", + "usesAverageCPUConstant": false + }, + { + "cloudProvider": "AWS", + "accountName": "aws account 1", + "serviceName": "s3", + "kilowattHours": 0.00098701927251199, + "co2e": 4.052780094476032e-7, + "cost": 1.645103657701107, + "region": "us-east-2", + "usesAverageCPUConstant": false + }, + { + "cloudProvider": "AWS", + "accountName": "aws account 4", + "serviceName": "ec2", + "kilowattHours": 50.904310297531644, + "co2e": 0.016399688935624875, + "cost": 2.235818514499144, + "region": "us-west-1", + "usesAverageCPUConstant": false + }, + { + "cloudProvider": "AWS", + "accountName": "aws account 3", + "serviceName": "rds", + "kilowattHours": 50.12852654254913, + "co2e": 0.016149757010633425, + "cost": 1.5786907055484218, + "region": "us-west-2", + "usesAverageCPUConstant": false + }, + { + "cloudProvider": "AWS", + "accountName": "aws account 2", + "serviceName": "lambda", + "kilowattHours": 59.54349975897656, + "co2e": 0.022571094910135484, + "cost": 2.324453671310117, + "region": "us-east-1", + "usesAverageCPUConstant": false + }, + { + "cloudProvider": "GCP", + "accountName": "gcp account 3", + "serviceName": "computeEngine", + "kilowattHours": 63.69530490695243, + "co2e": 0.03057374635533717, + "cost": 2.4104193295098923, + "region": "us-east1", + "usesAverageCPUConstant": false + }, + { + "cloudProvider": "AZURE", + "accountName": "azure account 0", + "serviceName": "virtualMachines", + "kilowattHours": 65.60925208439582, + "co2e": 0.014762081718989059, + "cost": 2.4070137425368685, + "region": "UK South", + "usesAverageCPUConstant": false + }, + { + "cloudProvider": "AliCloud", + "accountName": "ali account 1", + "serviceName": "virtualMachines", + "kilowattHours": 71.05357763407657, + "co2e": 0.014524032067235646, + "cost": 2.474481284318876, + "region": "cn-hangzhou", + "usesAverageCPUConstant": false + } + ] + }, + { + "timestamp": "2022-10-01T06:00:00.000Z", + "serviceEstimates": [ + { + "cloudProvider": "AWS", + "accountName": "aws account 4", + "serviceName": "ebs", + "kilowattHours": 0.7421818551593697, + "co2e": 0.00028133813365340714, + "cost": 2.498136748374863, + "region": "us-east-1", + "usesAverageCPUConstant": false + }, + { + "cloudProvider": "AWS", + "accountName": "aws account 1", + "serviceName": "s3", + "kilowattHours": 0.0002180651158962681, + "co2e": 8.953928110793486e-8, + "cost": 1.7632736153406632, + "region": "us-east-2", + "usesAverageCPUConstant": false + }, + { + "cloudProvider": "AWS", + "accountName": "aws account 0", + "serviceName": "ec2", + "kilowattHours": 54.239336047864754, + "co2e": 0.017474124176532442, + "cost": 1.9661288302344184, + "region": "us-west-1", + "usesAverageCPUConstant": false + }, + { + "cloudProvider": "AWS", + "accountName": "aws account 0", + "serviceName": "rds", + "kilowattHours": 50.23120235816861, + "co2e": 0.016182835770124106, + "cost": 2.0142111162898986, + "region": "us-west-2", + "usesAverageCPUConstant": false + }, + { + "cloudProvider": "AWS", + "accountName": "aws account 3", + "serviceName": "lambda", + "kilowattHours": 50.86615400739505, + "co2e": 0.019281782133429234, + "cost": 2.164622206937599, + "region": "us-east-1", + "usesAverageCPUConstant": false + }, + { + "cloudProvider": "GCP", + "accountName": "gcp account 3", + "serviceName": "computeEngine", + "kilowattHours": 59.19683162717123, + "co2e": 0.02841447918104219, + "cost": 1.6906306697783389, + "region": "us-east1", + "usesAverageCPUConstant": false + }, + { + "cloudProvider": "AZURE", + "accountName": "azure account 2", + "serviceName": "virtualMachines", + "kilowattHours": 50.27713613081028, + "co2e": 0.011312355629432311, + "cost": 1.7863670364270168, + "region": "UK South", + "usesAverageCPUConstant": false + }, + { + "cloudProvider": "AliCloud", + "accountName": "ali account 1", + "serviceName": "virtualMachines", + "kilowattHours": 76.05357763407657, + "co2e": 0.016524032067235646, + "cost": 2.974481284318876, + "region": "cn-hangzhou", + "usesAverageCPUConstant": false + } + ] + }, + { + "timestamp": "2022-09-01T06:00:00.000Z", + "serviceEstimates": [ + { + "cloudProvider": "AWS", + "accountName": "aws account 0", + "serviceName": "ebs", + "kilowattHours": 0.5648293345133781, + "co2e": 0.00021410929100465172, + "cost": 2.3304265345916653, + "region": "us-east-1", + "usesAverageCPUConstant": false + }, + { + "cloudProvider": "AWS", + "accountName": "aws account 4", + "serviceName": "s3", + "kilowattHours": 0.00005263625919629122, + "co2e": 2.161286911607075e-8, + "cost": 1.6038362843689178, + "region": "us-east-2", + "usesAverageCPUConstant": false + }, + { + "cloudProvider": "AWS", + "accountName": "aws account 0", + "serviceName": "ec2", + "kilowattHours": 64.1169430041115, + "co2e": 0.02065636317680559, + "cost": 1.8957903016901754, + "region": "us-west-1", + "usesAverageCPUConstant": false + }, + { + "cloudProvider": "AWS", + "accountName": "aws account 1", + "serviceName": "rds", + "kilowattHours": 50.69439982629539, + "co2e": 0.016332062708838108, + "cost": 2.3649041975938676, + "region": "us-west-2", + "usesAverageCPUConstant": false + }, + { + "cloudProvider": "AWS", + "accountName": "aws account 4", + "serviceName": "lambda", + "kilowattHours": 50.69873776324742, + "co2e": 0.01921831982517644, + "cost": 2.1212012771669846, + "region": "us-east-1", + "usesAverageCPUConstant": false + }, + { + "cloudProvider": "GCP", + "accountName": "gcp account 2", + "serviceName": "computeEngine", + "kilowattHours": 50.71620115791394, + "co2e": 0.024343776555798693, + "cost": 2.0052165772682793, + "region": "us-east1", + "usesAverageCPUConstant": false + }, + { + "cloudProvider": "AZURE", + "accountName": "azure account 0", + "serviceName": "virtualMachines", + "kilowattHours": 50.146548832423804, + "co2e": 0.011282973487295355, + "cost": 2.4727258099898037, + "region": "UK South", + "usesAverageCPUConstant": false + }, + { + "cloudProvider": "AliCloud", + "accountName": "ali account 1", + "serviceName": "virtualMachines", + "kilowattHours": 60.05357763407657, + "co2e": 0.013524032067235646, + "cost": 2.474481284318876, + "region": "cn-hangzhou", + "usesAverageCPUConstant": false + } + ] + }, + { + "timestamp": "2022-08-01T06:00:00.000Z", + "serviceEstimates": [ + { + "cloudProvider": "AWS", + "accountName": "aws account 0", + "serviceName": "ebs", + "kilowattHours": 0.35269028620932774, + "co2e": 0.00013369395410308364, + "cost": 2.426536030208615, + "region": "us-east-1", + "usesAverageCPUConstant": false + }, + { + "cloudProvider": "AWS", + "accountName": "aws account 2", + "serviceName": "s3", + "kilowattHours": 0.00007772936246220752, + "co2e": 3.1916298061882105e-8, + "cost": 1.9926068310477163, + "region": "us-east-2", + "usesAverageCPUConstant": false + }, + { + "cloudProvider": "AWS", + "accountName": "aws account 4", + "serviceName": "ec2", + "kilowattHours": 50.00593572462313, + "co2e": 0.016110262294594658, + "cost": 1.9838511921282318, + "region": "us-west-1", + "usesAverageCPUConstant": false + }, + { + "cloudProvider": "AWS", + "accountName": "aws account 3", + "serviceName": "rds", + "kilowattHours": 50.803461347266776, + "co2e": 0.016367198731864895, + "cost": 2.229003808663734, + "region": "us-west-2", + "usesAverageCPUConstant": false + }, + { + "cloudProvider": "AWS", + "accountName": "aws account 0", + "serviceName": "lambda", + "kilowattHours": 52.62976125953012, + "co2e": 0.019950310970888823, + "cost": 1.6972395592555778, + "region": "us-east-1", + "usesAverageCPUConstant": false + }, + { + "cloudProvider": "GCP", + "accountName": "gcp account 4", + "serviceName": "computeEngine", + "kilowattHours": 64.63052755166217, + "co2e": 0.03102265322479784, + "cost": 2.30459191166866, + "region": "us-east1", + "usesAverageCPUConstant": false + }, + { + "cloudProvider": "AZURE", + "accountName": "azure account 1", + "serviceName": "virtualMachines", + "kilowattHours": 53.31425378242701, + "co2e": 0.011995707101046077, + "cost": 1.6930870858502807, + "region": "UK South", + "usesAverageCPUConstant": false + }, + { + "cloudProvider": "AliCloud", + "accountName": "ali account 1", + "serviceName": "virtualMachines", + "kilowattHours": 55.05357763407657, + "co2e": 0.012524032067235646, + "cost": 2.274481284318876, + "region": "cn-hangzhou", + "usesAverageCPUConstant": false + } + ] + }, + { + "timestamp": "2022-07-01T06:00:00.000Z", + "serviceEstimates": [ + { + "cloudProvider": "AWS", + "accountName": "aws account 3", + "serviceName": "ebs", + "kilowattHours": 0.3793961762889928, + "co2e": 0.00014381732914969223, + "cost": 1.5660349090236223, + "region": "us-east-1", + "usesAverageCPUConstant": false + }, + { + "cloudProvider": "AWS", + "accountName": "aws account 2", + "serviceName": "s3", + "kilowattHours": 0.0007881261941471649, + "co2e": 3.2361092032637913e-7, + "cost": 2.107104300712024, + "region": "us-east-2", + "usesAverageCPUConstant": false + }, + { + "cloudProvider": "AWS", + "accountName": "aws account 3", + "serviceName": "ec2", + "kilowattHours": 50.4099085739953, + "co2e": 0.016240409015558344, + "cost": 1.7912671435326524, + "region": "us-west-1", + "usesAverageCPUConstant": false + }, + { + "cloudProvider": "AWS", + "accountName": "aws account 1", + "serviceName": "rds", + "kilowattHours": 56.61135475563019, + "co2e": 0.01823831032755711, + "cost": 2.0547082075494254, + "region": "us-west-2", + "usesAverageCPUConstant": false + }, + { + "cloudProvider": "AWS", + "accountName": "aws account 4", + "serviceName": "lambda", + "kilowattHours": 50.16319007556745, + "co2e": 0.019015310298755278, + "cost": 2.3442440999125886, + "region": "us-east-1", + "usesAverageCPUConstant": false + }, + { + "cloudProvider": "GCP", + "accountName": "gcp account 1", + "serviceName": "computeEngine", + "kilowattHours": 50.2235650164992, + "co2e": 0.02410731120791962, + "cost": 1.6300863324887378, + "region": "us-east1", + "usesAverageCPUConstant": false + }, + { + "cloudProvider": "AZURE", + "accountName": "azure account 3", + "serviceName": "virtualMachines", + "kilowattHours": 50.27485053251475, + "co2e": 0.011311841369815818, + "cost": 1.7666414199697307, + "region": "UK South", + "usesAverageCPUConstant": false + }, + { + "cloudProvider": "AliCloud", + "accountName": "ali account 1", + "serviceName": "virtualMachines", + "kilowattHours": 58.05357763407657, + "co2e": 0.013524032067235646, + "cost": 2.474481284318876, + "region": "cn-hangzhou", + "usesAverageCPUConstant": false + } + ] + }, + { + "timestamp": "2022-06-01T06:00:00.000Z", + "serviceEstimates": [ + { + "cloudProvider": "AWS", + "accountName": "aws account 3", + "serviceName": "ebs", + "kilowattHours": 0.0025963667867079376, + "co2e": 9.842021614705911e-7, + "cost": 2.110850189120251, + "region": "us-east-1", + "usesAverageCPUConstant": false + }, + { + "cloudProvider": "AWS", + "accountName": "aws account 3", + "serviceName": "s3", + "kilowattHours": 0.00038988295545011175, + "co2e": 1.600890605714595e-7, + "cost": 1.750590801038299, + "region": "us-east-2", + "usesAverageCPUConstant": false + }, + { + "cloudProvider": "AWS", + "accountName": "aws account 4", + "serviceName": "ec2", + "kilowattHours": 50.58419719553395, + "co2e": 0.016296559057893588, + "cost": 2.331961666410219, + "region": "us-west-1", + "usesAverageCPUConstant": false + }, + { + "cloudProvider": "AWS", + "accountName": "aws account 1", + "serviceName": "rds", + "kilowattHours": 50.09219672002729, + "co2e": 0.01613805274070103, + "cost": 2.171231385841728, + "region": "us-west-2", + "usesAverageCPUConstant": false + }, + { + "cloudProvider": "AWS", + "accountName": "aws account 0", + "serviceName": "lambda", + "kilowattHours": 50.34788323768369, + "co2e": 0.01908532175102552, + "cost": 2.178860527900543, + "region": "us-east-1", + "usesAverageCPUConstant": false + }, + { + "cloudProvider": "GCP", + "accountName": "gcp account 2", + "serviceName": "computeEngine", + "kilowattHours": 50.876621782936894, + "co2e": 0.02442077845580971, + "cost": 2.008009566294815, + "region": "us-east1", + "usesAverageCPUConstant": false + }, + { + "cloudProvider": "AZURE", + "accountName": "azure account 2", + "serviceName": "virtualMachines", + "kilowattHours": 69.14280006058016, + "co2e": 0.015557130013630535, + "cost": 2.159049075087805, + "region": "UK South", + "usesAverageCPUConstant": false + }, + { + "cloudProvider": "AliCloud", + "accountName": "ali account 1", + "serviceName": "virtualMachines", + "kilowattHours": 65.05357763407657, + "co2e": 0.014524032067235646, + "cost": 2.664481284318876, + "region": "cn-hangzhou", + "usesAverageCPUConstant": false + } + ] + }, + { + "timestamp": "2022-05-01T06:00:00.000Z", + "serviceEstimates": [ + { + "cloudProvider": "AWS", + "accountName": "aws account 3", + "serviceName": "ebs", + "kilowattHours": 0.847821319053617, + "co2e": 0.00032138277959233556, + "cost": 1.8756121359502171, + "region": "us-east-1", + "usesAverageCPUConstant": false + }, + { + "cloudProvider": "AWS", + "accountName": "aws account 3", + "serviceName": "s3", + "kilowattHours": 0.000630093974723783, + "co2e": 2.587216267733831e-7, + "cost": 2.3698572941873914, + "region": "us-east-2", + "usesAverageCPUConstant": false + }, + { + "cloudProvider": "AWS", + "accountName": "aws account 3", + "serviceName": "ec2", + "kilowattHours": 50.10307382046631, + "co2e": 0.016141556983518168, + "cost": 2.351022985532233, + "region": "us-west-1", + "usesAverageCPUConstant": false + }, + { + "cloudProvider": "AWS", + "accountName": "aws account 2", + "serviceName": "rds", + "kilowattHours": 50.22293041559674, + "co2e": 0.016180170823201556, + "cost": 1.5327957463127884, + "region": "us-west-2", + "usesAverageCPUConstant": false + }, + { + "cloudProvider": "AWS", + "accountName": "aws account 3", + "serviceName": "lambda", + "kilowattHours": 68.18586584927158, + "co2e": 0.025847147981617528, + "cost": 2.205957045144824, + "region": "us-east-1", + "usesAverageCPUConstant": false + }, + { + "cloudProvider": "GCP", + "accountName": "gcp account 4", + "serviceName": "computeEngine", + "kilowattHours": 68.47759811709409, + "co2e": 0.032869247096205166, + "cost": 2.026647361534485, + "region": "us-east1", + "usesAverageCPUConstant": false + }, + { + "cloudProvider": "AZURE", + "accountName": "azure account 1", + "serviceName": "virtualMachines", + "kilowattHours": 50.5125618456214, + "co2e": 0.011365326415264814, + "cost": 1.9833785822656285, + "region": "UK South", + "usesAverageCPUConstant": false + }, + { + "cloudProvider": "AliCloud", + "accountName": "ali account 1", + "serviceName": "virtualMachines", + "kilowattHours": 58.05357763407657, + "co2e": 0.013224032067235646, + "cost": 2.554481284318876, + "region": "cn-hangzhou", + "usesAverageCPUConstant": false + } + ] + }, + { + "timestamp": "2022-04-01T06:00:00.000Z", + "serviceEstimates": [ + { + "cloudProvider": "AWS", + "accountName": "aws account 1", + "serviceName": "ebs", + "kilowattHours": 0.9119831149849784, + "co2e": 0.00034570452741424076, + "cost": 1.7466850895849164, + "region": "us-east-1", + "usesAverageCPUConstant": false + }, + { + "cloudProvider": "AWS", + "accountName": "aws account 1", + "serviceName": "s3", + "kilowattHours": 0.00010168764085273696, + "co2e": 4.175375883526062e-8, + "cost": 2.0929755865584747, + "region": "us-east-2", + "usesAverageCPUConstant": false + }, + { + "cloudProvider": "AWS", + "accountName": "aws account 2", + "serviceName": "ec2", + "kilowattHours": 50.13033413155082, + "co2e": 0.01615033935615933, + "cost": 2.030781896188273, + "region": "us-west-1", + "usesAverageCPUConstant": false + }, + { + "cloudProvider": "AWS", + "accountName": "aws account 3", + "serviceName": "rds", + "kilowattHours": 53.81264913939785, + "co2e": 0.017336659735292387, + "cost": 1.7863504430322306, + "region": "us-west-2", + "usesAverageCPUConstant": false + }, + { + "cloudProvider": "AWS", + "accountName": "aws account 4", + "serviceName": "lambda", + "kilowattHours": 63.45075892152083, + "co2e": 0.02405221573362198, + "cost": 1.8740007850011755, + "region": "us-east-1", + "usesAverageCPUConstant": false + }, + { + "cloudProvider": "GCP", + "accountName": "gcp account 4", + "serviceName": "computeEngine", + "kilowattHours": 50.77403726191187, + "co2e": 0.0243715378857177, + "cost": 1.7839515021969587, + "region": "us-east1", + "usesAverageCPUConstant": false + }, + { + "cloudProvider": "AZURE", + "accountName": "azure account 4", + "serviceName": "virtualMachines", + "kilowattHours": 50.373127573933836, + "co2e": 0.011333953704135112, + "cost": 1.8199290662411896, + "region": "UK South", + "usesAverageCPUConstant": false + }, + { + "cloudProvider": "AliCloud", + "accountName": "ali account 1", + "serviceName": "virtualMachines", + "kilowattHours": 72.05357763407657, + "co2e": 0.015524032067235646, + "cost": 2.874481284318876, + "region": "cn-hangzhou", + "usesAverageCPUConstant": false + } + ] + }, + { + "timestamp": "2022-03-01T07:00:00.000Z", + "serviceEstimates": [ + { + "cloudProvider": "AWS", + "accountName": "aws account 1", + "serviceName": "ebs", + "kilowattHours": 0.8148394263939371, + "co2e": 0.00030888036652372336, + "cost": 1.9952062784974551, + "region": "us-east-1", + "usesAverageCPUConstant": false + }, + { + "cloudProvider": "AWS", + "accountName": "aws account 1", + "serviceName": "s3", + "kilowattHours": 0.00010100550904187954, + "co2e": 4.147367005666808e-8, + "cost": 2.479801470978538, + "region": "us-east-2", + "usesAverageCPUConstant": false + }, + { + "cloudProvider": "AWS", + "accountName": "aws account 1", + "serviceName": "ec2", + "kilowattHours": 50.933103987612505, + "co2e": 0.016408965312377156, + "cost": 2.014818576889102, + "region": "us-west-1", + "usesAverageCPUConstant": false + }, + { + "cloudProvider": "AWS", + "accountName": "aws account 2", + "serviceName": "rds", + "kilowattHours": 50.92736661108625, + "co2e": 0.016407116918993825, + "cost": 2.0552869714092585, + "region": "us-west-2", + "usesAverageCPUConstant": false + }, + { + "cloudProvider": "AWS", + "accountName": "aws account 2", + "serviceName": "lambda", + "kilowattHours": 70.47173348463286, + "co2e": 0.026713649540286294, + "cost": 1.8418849726705238, + "region": "us-east-1", + "usesAverageCPUConstant": false + }, + { + "cloudProvider": "GCP", + "accountName": "gcp account 3", + "serviceName": "computeEngine", + "kilowattHours": 57.666701915218034, + "co2e": 0.02768001691930466, + "cost": 1.611910606014586, + "region": "us-east1", + "usesAverageCPUConstant": false + }, + { + "cloudProvider": "AZURE", + "accountName": "azure account 2", + "serviceName": "virtualMachines", + "kilowattHours": 50.773157853289746, + "co2e": 0.011423960516990192, + "cost": 2.2728625664822433, + "region": "UK South", + "usesAverageCPUConstant": false + }, + { + "cloudProvider": "AliCloud", + "accountName": "ali account 1", + "serviceName": "virtualMachines", + "kilowattHours": 55.05357763407657, + "co2e": 0.013524032067235646, + "cost": 2.434481284318876, + "region": "cn-hangzhou", + "usesAverageCPUConstant": false + } + ] + } + ], + "recommendations": [ + { + "cloudProvider": "AWS", + "accountId": "aws account 0", + "accountName": "aws account 0", + "region": "us-west-1", + "recommendationType": "Modify", + "instanceName": "example-instance-5", + "recommendationDetail": "Modify instance: example-instance-5.", + "resourceId": "i-0f12345678912b12I", + "kilowattHourSavings": 116.513, + "costSavings": 3.611, + "co2eSavings": 0.037536643671 + }, + { + "cloudProvider": "AWS", + "accountId": "aws account 1", + "accountName": "aws account 1", + "region": "us-east-2", + "recommendationType": "Modify", + "instanceName": "example-instance", + "recommendationDetail": "Modify instance: example-instance.", + "resourceId": "i-0f12345678912b12I", + "kilowattHourSavings": 114.978, + "costSavings": 13.506, + "co2eSavings": 0.047210886624 + }, + { + "cloudProvider": "AWS", + "accountId": "aws account 2", + "accountName": "aws account 2", + "region": "us-east-1", + "recommendationType": "Modify", + "instanceName": "example-instance-2", + "recommendationDetail": "Modify instance: example-instance-2.", + "resourceId": "i-0f12345678912b12I", + "kilowattHourSavings": 18.419, + "costSavings": 5.667, + "co2eSavings": 0.0069820719110000005 + }, + { + "cloudProvider": "AWS", + "accountId": "aws account 3", + "accountName": "aws account 3", + "region": "us-west-2", + "recommendationType": "Delete", + "instanceName": "example-instance", + "recommendationDetail": "Delete instance: example-instance.", + "resourceId": "i-0f12345678912b12I", + "kilowattHourSavings": 11.195, + "costSavings": 4.442, + "co2eSavings": 0.003606659565 + }, + { + "cloudProvider": "AWS", + "accountId": "aws account 4", + "accountName": "aws account 4", + "region": "us-west-1", + "recommendationType": "Delete", + "instanceName": "example-instance", + "recommendationDetail": "Delete instance: example-instance.", + "resourceId": "i-0f12345678912b12I", + "kilowattHourSavings": 111.717, + "costSavings": 5.788, + "co2eSavings": 0.035991530739 + }, + { + "cloudProvider": "GCP", + "accountId": "gcp account 0", + "accountName": "gcp account 0", + "region": "us-west1", + "recommendationType": "DELETE_IMAGE", + "instanceName": "test-instance-1", + "recommendationDetail": "Save cost by performing a DELETE_IMAGE for instance: test-instance-1.", + "resourceId": 6906976106869124000, + "kilowattHourSavings": 12.081, + "costSavings": 1.314, + "co2eSavings": 0.0009423179999999999 + }, + { + "cloudProvider": "GCP", + "accountId": "gcp account 1", + "accountName": "gcp account 1", + "region": "us-west1", + "recommendationType": "SNAPSHOT_AND_DELETE_DISK", + "instanceName": "test-instance-3", + "recommendationDetail": "Save cost by performing a SNAPSHOT_AND_DELETE_DISK for instance: test-instance-3.", + "resourceId": 4256745502855943000, + "kilowattHourSavings": 18.742, + "costSavings": 4.549, + "co2eSavings": 0.001461876 + }, + { + "cloudProvider": "GCP", + "accountId": "gcp account 2", + "accountName": "gcp account 2", + "region": "us-west1", + "recommendationType": "CHANGE_MACHINE_TYPE", + "instanceName": "test-instance-10", + "recommendationDetail": "Save cost by performing a CHANGE_MACHINE_TYPE for instance: test-instance-10.", + "resourceId": 9625521363699055000, + "kilowattHourSavings": 16.989, + "costSavings": 8.165, + "co2eSavings": 0.001325142 + }, + { + "cloudProvider": "GCP", + "accountId": "gcp account 3", + "accountName": "gcp account 3", + "region": "us-east1", + "recommendationType": "DELETE_ADDRESS", + "instanceName": "test-instance-8", + "recommendationDetail": "Save cost by performing a DELETE_ADDRESS for instance: test-instance-8.", + "resourceId": 9351508929180877000, + "kilowattHourSavings": 19.946, + "costSavings": 5.2, + "co2eSavings": 0.00957408 + }, + { + "cloudProvider": "GCP", + "accountId": "gcp account 4", + "accountName": "gcp account 4", + "region": "us-west2", + "recommendationType": "DELETE_DISK", + "instanceName": "test-instance-14", + "recommendationDetail": "Save cost by performing a DELETE_DISK for instance: test-instance-14.", + "resourceId": 7840914330904416000, + "kilowattHourSavings": 18.782, + "costSavings": 8.359, + "co2eSavings": 0.0047518460000000005 + }, + { + "cloudProvider": "GCP", + "accountId": "gcp account 2", + "accountName": "gcp account 2", + "region": "us-east1", + "recommendationType": "STOP_VM", + "instanceName": "test-instance-9", + "recommendationDetail": "Save cost by performing a STOP_VM for instance: test-instance-9.", + "resourceId": 8928403120086348000, + "kilowattHourSavings": 116.483, + "costSavings": 8.409, + "co2eSavings": 0.055911840000000004 + }, + { + "cloudProvider": "GCP", + "accountId": "gcp account 4", + "accountName": "gcp account 4", + "region": "us-east1", + "recommendationType": "STOP_VM", + "instanceName": "test-instance-9", + "recommendationDetail": "Save cost by performing a STOP_VM for instance: test-instance-9.", + "resourceId": 8928403120086348000, + "kilowattHourSavings": 0.0001, + "costSavings": 0.0002, + "co2eSavings": 4.8000000000000006e-8 + }, + { + "cloudProvider": "GCP", + "accountId": "gcp account 4", + "accountName": "gcp account 4", + "region": "us-east1", + "recommendationType": "STOP_VM", + "instanceName": "test-instance-9", + "recommendationDetail": "Save cost by performing a STOP_VM for instance: test-instance-9.", + "resourceId": 8928403120086348000, + "kilowattHourSavings": 0, + "costSavings": 0, + "co2eSavings": 0 + }, + { + "cloudProvider": "GCP", + "accountId": "gcp account 4", + "accountName": "gcp account 4", + "region": "us-east1", + "recommendationType": "STOP_VM", + "instanceName": "test-instance-9", + "recommendationDetail": "Save cost by performing a STOP_VM for instance: test-instance-9.", + "resourceId": 8928403120086348000, + "kilowattHourSavings": 0.001, + "costSavings": 0.001, + "co2eSavings": 4.800000000000001e-7 + }, + { + "cloudProvider": "GCP", + "accountId": "gcp account 4", + "accountName": "gcp account 4", + "region": "us-east1", + "recommendationType": "STOP_VM", + "instanceName": "test-instance-9", + "recommendationDetail": "Save cost by performing a STOP_VM for instance: test-instance-9.", + "resourceId": 8928403120086348000, + "kilowattHourSavings": 0.002, + "costSavings": 0.002, + "co2eSavings": 9.600000000000001e-7 + } + ] +} From ef57775dea9db7231340417a86439da91ef7ae9a Mon Sep 17 00:00:00 2001 From: jianglinwang Date: Tue, 14 Mar 2023 17:12:42 +0800 Subject: [PATCH 082/251] add ali cost and usage service --- packages/ali/src/application/AliAccount.ts | 37 +++++++++++++-- .../domain/AliFootprintEstimationConstants.ts | 2 +- .../ali/src/lib/AliCostAndUsageService.ts | 46 +++++++++++++++++++ packages/ali/src/lib/index.ts | 4 ++ 4 files changed, 84 insertions(+), 5 deletions(-) create mode 100644 packages/ali/src/lib/AliCostAndUsageService.ts create mode 100644 packages/ali/src/lib/index.ts diff --git a/packages/ali/src/application/AliAccount.ts b/packages/ali/src/application/AliAccount.ts index 4851b797c..842be8e9b 100644 --- a/packages/ali/src/application/AliAccount.ts +++ b/packages/ali/src/application/AliAccount.ts @@ -2,14 +2,26 @@ * © 2023 Thoughtworks, Inc. */ -import { CloudProviderAccount } from '@cloud-carbon-footprint/core' -import { EstimationResult, GroupBy } from '@cloud-carbon-footprint/common' +import { + CloudProviderAccount, + ComputeEstimator, + EmbodiedEmissionsEstimator, + MemoryEstimator, + NetworkingEstimator, + StorageEstimator, + UnknownEstimator, +} from '@cloud-carbon-footprint/core' +import { EstimationResult, GroupBy, Logger } from '@cloud-carbon-footprint/common' +import AliCostAndUsageService from '../lib/AliCostAndUsageService' +import { ALI_CLOUD_CONSTANTS } from '../domain' export default class AliAccount extends CloudProviderAccount { private readonly credentials: any + private logger: Logger constructor() { super() + this.logger = new Logger('ALIAccount') } async getDataForRegions( @@ -17,6 +29,9 @@ export default class AliAccount extends CloudProviderAccount { endDate: Date, grouping: GroupBy, ): Promise { + this.logger.info( + `startDate: ${startDate}, endDate: ${endDate}, grouping: ${grouping}`, + ) return null } @@ -25,7 +40,21 @@ export default class AliAccount extends CloudProviderAccount { endDate: Date, grouping: GroupBy, ): Promise { - console.log(startDate, endDate, grouping) - return null + this.logger.info( + `startDate: ${startDate}, endDate: ${endDate}, grouping: ${grouping}`, + ) + + const aliCostAndUsageService = new AliCostAndUsageService( + new ComputeEstimator(), + new StorageEstimator(ALI_CLOUD_CONSTANTS.SSDCOEFFICIENT), + new StorageEstimator(ALI_CLOUD_CONSTANTS.HDDCOEFFICIENT), + new NetworkingEstimator(ALI_CLOUD_CONSTANTS.NETWORKING_COEFFICIENT), + new MemoryEstimator(ALI_CLOUD_CONSTANTS.MEMORY_COEFFICIENT), + new UnknownEstimator(ALI_CLOUD_CONSTANTS.ESTIMATE_UNKNOWN_USAGE_BY), + new EmbodiedEmissionsEstimator( + ALI_CLOUD_CONSTANTS.SERVER_EXPECTED_LIFESPAN, + ), + ) + return aliCostAndUsageService.getEstimates(startDate, endDate, grouping) } } diff --git a/packages/ali/src/domain/AliFootprintEstimationConstants.ts b/packages/ali/src/domain/AliFootprintEstimationConstants.ts index 461f805eb..76c2c6428 100644 --- a/packages/ali/src/domain/AliFootprintEstimationConstants.ts +++ b/packages/ali/src/domain/AliFootprintEstimationConstants.ts @@ -118,7 +118,7 @@ export const ALI_CLOUD_CONSTANTS: CloudConstantsByProvider = { }, NETWORKING_COEFFICIENT: 0.001, // kWh / Gb MEMORY_COEFFICIENT: 0.000392, // kWh / Gb - PUE_AVG: 1.135, + PUE_AVG: 1.3, getPUE: (): number => { return ALI_CLOUD_CONSTANTS.PUE_AVG }, diff --git a/packages/ali/src/lib/AliCostAndUsageService.ts b/packages/ali/src/lib/AliCostAndUsageService.ts new file mode 100644 index 000000000..1130016cb --- /dev/null +++ b/packages/ali/src/lib/AliCostAndUsageService.ts @@ -0,0 +1,46 @@ +/* + * © 2023 Thoughtworks, Inc. + */ + +import { + ComputeEstimator, + EmbodiedEmissionsEstimator, + MemoryEstimator, + NetworkingEstimator, + StorageEstimator, + UnknownEstimator, +} from '@cloud-carbon-footprint/core' +import { ServiceWrapper } from '@cloud-carbon-footprint/aws' +import { + EstimationResult, + GroupBy, + Logger, +} from '@cloud-carbon-footprint/common' + +export default class AliCostAndUsageService { + private readonly logger: Logger + + constructor( + private readonly computeEstimator: ComputeEstimator, + private readonly ssdStorageEstimator: StorageEstimator, + private readonly hddStorageEstimator: StorageEstimator, + private readonly networkingEstimator: NetworkingEstimator, + private readonly memoryEstimator: MemoryEstimator, + private readonly unknownEstimator: UnknownEstimator, + private readonly embodiedEmissionsEstimator: EmbodiedEmissionsEstimator, + private readonly serviceWrapper?: ServiceWrapper, + ) { + this.logger = new Logger('AliCostAndUsageService') + } + + getEstimates( + start: Date, + end: Date, + grouping: GroupBy, + ): Promise { + this.logger.info( + `startDate: ${start}, endDate: ${end}, grouping: ${grouping}`, + ) + return null + } +} diff --git a/packages/ali/src/lib/index.ts b/packages/ali/src/lib/index.ts new file mode 100644 index 000000000..3f6cfeead --- /dev/null +++ b/packages/ali/src/lib/index.ts @@ -0,0 +1,4 @@ +/* + * © 2023 Thoughtworks, Inc. + */ +export { default as AliCostAndUsageService } from './AliCostAndUsageService' \ No newline at end of file From dee885638b956313883e98a292eec432413a9d15 Mon Sep 17 00:00:00 2001 From: dean Date: Tue, 14 Mar 2023 15:38:57 +0800 Subject: [PATCH 083/251] add ali get cost class --- .talismanrc | 2 + packages/ali/package.json | 1 + .../ali/src/lib/AliCostAndUsageReports.ts | 68 +++++++++++++++++++ packages/common/src/Config.ts | 4 ++ 4 files changed, 75 insertions(+) create mode 100644 packages/ali/src/lib/AliCostAndUsageReports.ts diff --git a/.talismanrc b/.talismanrc index bd35f7729..9939bbaa4 100644 --- a/.talismanrc +++ b/.talismanrc @@ -113,6 +113,8 @@ fileignoreconfig: checksum: 4271fca662d95372f0f1439c4a5f82424a06e2c38790a8806e1031dc74387016 - filename: package.json checksum: ba58128b0b71af8219a05b5cf27fcad837639fdb85c8846be7ef8248b5124f1c +- filename: packages/ali/src/lib/AliCostAndUsageReports.ts + checksum: c968ea408b49bf483ce3e5906c2220f9c9c5b2624fb78633a8f9aa4ab24470bf - filename: packages/api/.env.template checksum: ec94dc4f50d599e64405562c74b0ba2d1f49781dba1d0fbc9f5011eeea7e2101 - filename: packages/api/create_docker_secrets.sh diff --git a/packages/ali/package.json b/packages/ali/package.json index 4ce86a5cc..47755b61a 100644 --- a/packages/ali/package.json +++ b/packages/ali/package.json @@ -41,6 +41,7 @@ "lint:fix": "eslint '*/**/*.ts' --quiet --fix" }, "dependencies": { + "@alicloud/bssopenapi20171214": "^1.0.12", "@cloud-carbon-footprint/common": "^1.9.0", "@cloud-carbon-footprint/core": "^0.17.0", "@google-cloud/iam-credentials": "^1.1.1", diff --git a/packages/ali/src/lib/AliCostAndUsageReports.ts b/packages/ali/src/lib/AliCostAndUsageReports.ts new file mode 100644 index 000000000..d0e36fdc3 --- /dev/null +++ b/packages/ali/src/lib/AliCostAndUsageReports.ts @@ -0,0 +1,68 @@ +/* + * © 2023 Thoughtworks, Inc. + */ + +import { Logger } from '@cloud-carbon-footprint/common' +import BssOpenApi20171214 from '@alicloud/bssopenapi20171214' +import * as $OpenApi from '@alicloud/openapi-client' +import * as $BssOpenApi20171214 from '@alicloud/bssopenapi20171214' +import * as $Util from '@alicloud/tea-util' +import moment from 'moment' +import { MutableServiceEstimate } from '@cloud-carbon-footprint/core/src/FootprintEstimate' + +export default class AliCostAndUsageReports { + private readonly aliCostAndUsageReportsLogger: Logger + + constructor() { + this.aliCostAndUsageReportsLogger = new Logger('aliCostAndUsageReports') + } + + async getEstimates(date: Date): Promise { + const results: MutableServiceEstimate[] = [] + + const response = await this.getUsage(date, '', '') + response.body.data.items.forEach((cur) => { + results.push({ + cloudProvider: 'ALI', + accountName: cur.billAccountName, + serviceName: cur.nickName, + kilowattHours: 0, + co2e: 0, + accountId: cur.billAccountID, + cost: 0, + region: cur.region, + usesAverageCPUConstant: false, + }) + }) + return results + } + + createClient( + accessKeyId: string, + accessKeySecret: string, + ): BssOpenApi20171214 { + const config = new $OpenApi.Config({ + accessKeyId: accessKeyId, + accessKeySecret: accessKeySecret, + }) + config.endpoint = `business.aliyuncs.com` + return new BssOpenApi20171214(config) + } + + async getUsage( + date: Date, + accessKeyId: string, + accessKeySecret: string, + ): Promise<$BssOpenApi20171214.DescribeInstanceBillResponse> { + const client = this.createClient(accessKeyId, accessKeySecret) + const describeInstanceBillRequest = + new $BssOpenApi20171214.DescribeInstanceBillRequest({ + billingCycle: moment(date).format('YYYY-MM-DD'), + }) + const runtime = new $Util.RuntimeOptions({}) + return await client.describeInstanceBillWithOptions( + describeInstanceBillRequest, + runtime, + ) + } +} diff --git a/packages/common/src/Config.ts b/packages/common/src/Config.ts index 83f684dd5..7e6609530 100644 --- a/packages/common/src/Config.ts +++ b/packages/common/src/Config.ts @@ -66,6 +66,10 @@ export interface CCFConfig { CURRENT_REGIONS?: string[] INCLUDE_ESTIMATES?: boolean USE_BILLING_DATA?: boolean + authentication?: { + accessKeyId: string + accessKeySecret: string + } } LOGGING_MODE?: string CACHE_MODE?: string From 8538679ab98165152e73548112baadad5bf1997c Mon Sep 17 00:00:00 2001 From: dean Date: Tue, 14 Mar 2023 16:50:58 +0800 Subject: [PATCH 084/251] add ali config ak and as --- packages/common/src/Config.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/common/src/Config.ts b/packages/common/src/Config.ts index 7e6609530..2ff994630 100644 --- a/packages/common/src/Config.ts +++ b/packages/common/src/Config.ts @@ -290,6 +290,10 @@ const getConfig = (): CCFConfig => ({ USE_BILLING_DATA: !!process.env.ALI_USE_BILLING_DATA && process.env.ALI_USE_BILLING_DATA !== 'false', + authentication: { + accessKeyId: process.env.ALI_ACCESS_KEY, + accessKeySecret: process.env.ALI_ACCESS_SECRET, + }, }, LOGGING_MODE: process.env.LOGGING_MODE || '', CACHE_MODE: getEnvVar('CACHE_MODE') || '', From e2feaf2ce3daca7ba3c18c07ff9252180158d1b6 Mon Sep 17 00:00:00 2001 From: dean Date: Wed, 15 Mar 2023 09:56:57 +0800 Subject: [PATCH 085/251] finish get estimates from ali cloud payment --- packages/ali/src/application/AliAccount.ts | 6 +- .../ali/src/lib/AliCostAndUsageReports.ts | 68 --------------- .../ali/src/lib/AliCostAndUsageService.ts | 83 ++++++++++++++++++- packages/common/src/Config.ts | 19 +++-- 4 files changed, 96 insertions(+), 80 deletions(-) delete mode 100644 packages/ali/src/lib/AliCostAndUsageReports.ts diff --git a/packages/ali/src/application/AliAccount.ts b/packages/ali/src/application/AliAccount.ts index 842be8e9b..be2e9a6b3 100644 --- a/packages/ali/src/application/AliAccount.ts +++ b/packages/ali/src/application/AliAccount.ts @@ -11,7 +11,11 @@ import { StorageEstimator, UnknownEstimator, } from '@cloud-carbon-footprint/core' -import { EstimationResult, GroupBy, Logger } from '@cloud-carbon-footprint/common' +import { + EstimationResult, + GroupBy, + Logger, +} from '@cloud-carbon-footprint/common' import AliCostAndUsageService from '../lib/AliCostAndUsageService' import { ALI_CLOUD_CONSTANTS } from '../domain' diff --git a/packages/ali/src/lib/AliCostAndUsageReports.ts b/packages/ali/src/lib/AliCostAndUsageReports.ts deleted file mode 100644 index d0e36fdc3..000000000 --- a/packages/ali/src/lib/AliCostAndUsageReports.ts +++ /dev/null @@ -1,68 +0,0 @@ -/* - * © 2023 Thoughtworks, Inc. - */ - -import { Logger } from '@cloud-carbon-footprint/common' -import BssOpenApi20171214 from '@alicloud/bssopenapi20171214' -import * as $OpenApi from '@alicloud/openapi-client' -import * as $BssOpenApi20171214 from '@alicloud/bssopenapi20171214' -import * as $Util from '@alicloud/tea-util' -import moment from 'moment' -import { MutableServiceEstimate } from '@cloud-carbon-footprint/core/src/FootprintEstimate' - -export default class AliCostAndUsageReports { - private readonly aliCostAndUsageReportsLogger: Logger - - constructor() { - this.aliCostAndUsageReportsLogger = new Logger('aliCostAndUsageReports') - } - - async getEstimates(date: Date): Promise { - const results: MutableServiceEstimate[] = [] - - const response = await this.getUsage(date, '', '') - response.body.data.items.forEach((cur) => { - results.push({ - cloudProvider: 'ALI', - accountName: cur.billAccountName, - serviceName: cur.nickName, - kilowattHours: 0, - co2e: 0, - accountId: cur.billAccountID, - cost: 0, - region: cur.region, - usesAverageCPUConstant: false, - }) - }) - return results - } - - createClient( - accessKeyId: string, - accessKeySecret: string, - ): BssOpenApi20171214 { - const config = new $OpenApi.Config({ - accessKeyId: accessKeyId, - accessKeySecret: accessKeySecret, - }) - config.endpoint = `business.aliyuncs.com` - return new BssOpenApi20171214(config) - } - - async getUsage( - date: Date, - accessKeyId: string, - accessKeySecret: string, - ): Promise<$BssOpenApi20171214.DescribeInstanceBillResponse> { - const client = this.createClient(accessKeyId, accessKeySecret) - const describeInstanceBillRequest = - new $BssOpenApi20171214.DescribeInstanceBillRequest({ - billingCycle: moment(date).format('YYYY-MM-DD'), - }) - const runtime = new $Util.RuntimeOptions({}) - return await client.describeInstanceBillWithOptions( - describeInstanceBillRequest, - runtime, - ) - } -} diff --git a/packages/ali/src/lib/AliCostAndUsageService.ts b/packages/ali/src/lib/AliCostAndUsageService.ts index 1130016cb..bba861ece 100644 --- a/packages/ali/src/lib/AliCostAndUsageService.ts +++ b/packages/ali/src/lib/AliCostAndUsageService.ts @@ -12,10 +12,17 @@ import { } from '@cloud-carbon-footprint/core' import { ServiceWrapper } from '@cloud-carbon-footprint/aws' import { + configLoader, EstimationResult, GroupBy, Logger, + ServiceData, } from '@cloud-carbon-footprint/common' +import BssOpenApi20171214 from '@alicloud/bssopenapi20171214' +import * as $OpenApi from '@alicloud/openapi-client' +import * as $BssOpenApi20171214 from '@alicloud/bssopenapi20171214' +import moment from 'moment/moment' +import * as $Util from '@alicloud/tea-util' export default class AliCostAndUsageService { private readonly logger: Logger @@ -33,7 +40,7 @@ export default class AliCostAndUsageService { this.logger = new Logger('AliCostAndUsageService') } - getEstimates( + async getEstimates( start: Date, end: Date, grouping: GroupBy, @@ -41,6 +48,78 @@ export default class AliCostAndUsageService { this.logger.info( `startDate: ${start}, endDate: ${end}, grouping: ${grouping}`, ) - return null + const result: EstimationResult[] = [] + const serviceEstimates: ServiceData[] = [] + const aliConfig = configLoader().ALI + + this.getDates(start, end).forEach((date) => { + this.getUsage( + date, + aliConfig.authentication.accessKeyId, + aliConfig.authentication.accessKeySecret, + ).then((response) => { + response.body.data.items.forEach((cur) => { + serviceEstimates.push({ + cloudProvider: 'ALI', + accountName: cur.billAccountName, + serviceName: cur.nickName, + accountId: cur.billAccountID, + // todo calculate + kilowattHours: 0, + co2e: 0, + cost: 0, + // todo chinese to english? + region: cur.region, + usesAverageCPUConstant: false, + }) + }) + }) + result.push({ + groupBy: grouping, + timestamp: date, + serviceEstimates, + }) + }) + + return result + } + + getDates(startDate: Date, endDate: Date) { + const dates = [] + const currentDate = new Date(startDate) + while (currentDate <= endDate) { + dates.push(new Date(currentDate)) + currentDate.setDate(currentDate.getDate() + 1) + } + return dates + } + + createClient( + accessKeyId: string, + accessKeySecret: string, + ): BssOpenApi20171214 { + const config = new $OpenApi.Config({ + accessKeyId: accessKeyId, + accessKeySecret: accessKeySecret, + }) + config.endpoint = `business.aliyuncs.com` + return new BssOpenApi20171214(config) + } + + async getUsage( + date: Date, + accessKeyId: string, + accessKeySecret: string, + ): Promise<$BssOpenApi20171214.DescribeInstanceBillResponse> { + const client = this.createClient(accessKeyId, accessKeySecret) + const describeInstanceBillRequest = + new $BssOpenApi20171214.DescribeInstanceBillRequest({ + billingCycle: moment(date).format('YYYY-MM'), + }) + const runtime = new $Util.RuntimeOptions({}) + return await client.describeInstanceBillWithOptions( + describeInstanceBillRequest, + runtime, + ) } } diff --git a/packages/common/src/Config.ts b/packages/common/src/Config.ts index 2ff994630..169aad3e2 100644 --- a/packages/common/src/Config.ts +++ b/packages/common/src/Config.ts @@ -4,6 +4,7 @@ import fs from 'fs' import dotenv from 'dotenv' import { AWS_RECOMMENDATIONS_SERVICES } from './RecommendationsService' + dotenv.config() export interface CCFConfig { @@ -153,9 +154,9 @@ const getEnvVar = (envVar: string): string => { const getConfig = (): CCFConfig => ({ AWS: { - INCLUDE_ESTIMATES: process.env.AWS_INCLUDE_ESTIMATES - ? !!process.env.AWS_INCLUDE_ESTIMATES - : true, + INCLUDE_ESTIMATES: + !!process.env.AWS_INCLUDE_ESTIMATES && + process.env.AWS_INCLUDE_ESTIMATES !== 'false', USE_BILLING_DATA: !!process.env.AWS_USE_BILLING_DATA && process.env.AWS_USE_BILLING_DATA !== 'false', @@ -241,9 +242,9 @@ const getConfig = (): CCFConfig => ({ USE_CARBON_FREE_ENERGY_PERCENTAGE: !!process.env.GCP_USE_CARBON_FREE_ENERGY_PERCENTAGE && process.env.GCP_USE_CARBON_FREE_ENERGY_PERCENTAGE !== 'false', - INCLUDE_ESTIMATES: process.env.GCP_INCLUDE_ESTIMATES - ? !!process.env.GCP_INCLUDE_ESTIMATES - : true, + INCLUDE_ESTIMATES: + !!process.env.GCP_INCLUDE_ESTIMATES && + process.env.GCP_INCLUDE_ESTIMATES !== 'false', USE_BILLING_DATA: !!process.env.GCP_USE_BILLING_DATA && process.env.GCP_USE_BILLING_DATA !== 'false', @@ -258,9 +259,9 @@ const getConfig = (): CCFConfig => ({ RESOURCE_TAG_NAMES: JSON.parse(getGCPResourceTagNames()), }, AZURE: { - INCLUDE_ESTIMATES: process.env.AZURE_INCLUDE_ESTIMATES - ? !!process.env.AZURE_INCLUDE_ESTIMATES - : true, + INCLUDE_ESTIMATES: + !!process.env.AZURE_INCLUDE_ESTIMATES && + process.env.AZURE_INCLUDE_ESTIMATES !== 'false', USE_BILLING_DATA: !!process.env.AZURE_USE_BILLING_DATA && process.env.AZURE_USE_BILLING_DATA !== 'false', From 3220bd8d268a884a165e864ff8cc49e01c694995 Mon Sep 17 00:00:00 2001 From: dean Date: Wed, 15 Mar 2023 09:58:49 +0800 Subject: [PATCH 086/251] undo talisman check sum --- .talismanrc | 2 -- 1 file changed, 2 deletions(-) diff --git a/.talismanrc b/.talismanrc index 9939bbaa4..bd35f7729 100644 --- a/.talismanrc +++ b/.talismanrc @@ -113,8 +113,6 @@ fileignoreconfig: checksum: 4271fca662d95372f0f1439c4a5f82424a06e2c38790a8806e1031dc74387016 - filename: package.json checksum: ba58128b0b71af8219a05b5cf27fcad837639fdb85c8846be7ef8248b5124f1c -- filename: packages/ali/src/lib/AliCostAndUsageReports.ts - checksum: c968ea408b49bf483ce3e5906c2220f9c9c5b2624fb78633a8f9aa4ab24470bf - filename: packages/api/.env.template checksum: ec94dc4f50d599e64405562c74b0ba2d1f49781dba1d0fbc9f5011eeea7e2101 - filename: packages/api/create_docker_secrets.sh From 85f9e5c873a28d423e51a1a605ee7ed146215745 Mon Sep 17 00:00:00 2001 From: dean Date: Wed, 15 Mar 2023 11:26:39 +0800 Subject: [PATCH 087/251] move call ali sdk to getEstimates --- packages/ali/src/__tests__/AliAccount.test.ts | 25 ++++++++----- .../ali/src/lib/AliCostAndUsageService.ts | 37 +++++++++---------- packages/ali/src/lib/index.ts | 2 +- 3 files changed, 35 insertions(+), 29 deletions(-) diff --git a/packages/ali/src/__tests__/AliAccount.test.ts b/packages/ali/src/__tests__/AliAccount.test.ts index b49a06fc7..8b9d302b3 100644 --- a/packages/ali/src/__tests__/AliAccount.test.ts +++ b/packages/ali/src/__tests__/AliAccount.test.ts @@ -21,14 +21,21 @@ describe('Ali Account', () => { expect(results).toEqual(null) }) - it('gets results from getDataFromCostAndUsageReports function', async () => { - const aliAccount = new AliAccount() - const results = await aliAccount.getDataFromCostAndUsageReports( - startDate, - endDate, - grouping, - ) - expect(results).toEqual(null) - }) + // it('gets results from getDataFromCostAndUsageReports function', async () => { + // const aliAccount = new AliAccount() + // const results = await aliAccount.getDataFromCostAndUsageReports( + // startDate, + // endDate, + // grouping, + // ) + // + // expect(results).toEqual([ + // { + // groupBy: 'day', + // serviceEstimates: [], + // timestamp: '2021-01-01T00:00:00.000Z', + // }, + // ]) + // }) }) diff --git a/packages/ali/src/lib/AliCostAndUsageService.ts b/packages/ali/src/lib/AliCostAndUsageService.ts index bba861ece..dfce2b70f 100644 --- a/packages/ali/src/lib/AliCostAndUsageService.ts +++ b/packages/ali/src/lib/AliCostAndUsageService.ts @@ -52,26 +52,26 @@ export default class AliCostAndUsageService { const serviceEstimates: ServiceData[] = [] const aliConfig = configLoader().ALI - this.getDates(start, end).forEach((date) => { - this.getUsage( + for (const date of this.getDates(start, end)) { + const response = await this.getUsage( date, aliConfig.authentication.accessKeyId, aliConfig.authentication.accessKeySecret, - ).then((response) => { - response.body.data.items.forEach((cur) => { - serviceEstimates.push({ - cloudProvider: 'ALI', - accountName: cur.billAccountName, - serviceName: cur.nickName, - accountId: cur.billAccountID, - // todo calculate - kilowattHours: 0, - co2e: 0, - cost: 0, - // todo chinese to english? - region: cur.region, - usesAverageCPUConstant: false, - }) + ) + if (response.body.data.totalCount <= 0) break + response.body.data.items.forEach((cur) => { + serviceEstimates.push({ + cloudProvider: 'ALI', + accountName: cur.billAccountName, + serviceName: cur.nickName, + accountId: cur.billAccountID, + // todo calculate + kilowattHours: 0, + co2e: 0, + cost: 0, + // todo chinese to english? + region: cur.region, + usesAverageCPUConstant: false, }) }) result.push({ @@ -79,8 +79,7 @@ export default class AliCostAndUsageService { timestamp: date, serviceEstimates, }) - }) - + } return result } diff --git a/packages/ali/src/lib/index.ts b/packages/ali/src/lib/index.ts index 3f6cfeead..e5775f6ff 100644 --- a/packages/ali/src/lib/index.ts +++ b/packages/ali/src/lib/index.ts @@ -1,4 +1,4 @@ /* * © 2023 Thoughtworks, Inc. */ -export { default as AliCostAndUsageService } from './AliCostAndUsageService' \ No newline at end of file +export { default as AliCostAndUsageService } from './AliCostAndUsageService' From 893fd64e48388c201bd21324e9df3b257cce2cd4 Mon Sep 17 00:00:00 2001 From: jianglinwang Date: Wed, 15 Mar 2023 11:11:44 +0800 Subject: [PATCH 088/251] add ali calculate row. --- packages/ali/src/lib/AliCalculateRow.ts | 34 +++++ .../ali/src/lib/AliCostAndUsageService.ts | 3 + packages/ali/src/lib/AliTypes.ts | 3 + yarn.lock | 140 +++++++++++++++++- 4 files changed, 178 insertions(+), 2 deletions(-) create mode 100644 packages/ali/src/lib/AliCalculateRow.ts create mode 100644 packages/ali/src/lib/AliTypes.ts diff --git a/packages/ali/src/lib/AliCalculateRow.ts b/packages/ali/src/lib/AliCalculateRow.ts new file mode 100644 index 000000000..5911a62f0 --- /dev/null +++ b/packages/ali/src/lib/AliCalculateRow.ts @@ -0,0 +1,34 @@ +/* + * © 2023 Thoughtworks, Inc. + */ + +import { BillingDataRow } from '@cloud-carbon-footprint/core' +import { configLoader } from '@cloud-carbon-footprint/common' +import { DescribeInstanceBillResponse } from '@alicloud/bssopenapi20171214' + +export default class AliCalculateRow extends BillingDataRow { + constructor(usageDetail: DescribeInstanceBillResponse) { + // const consumptionDetails = getConsumptionDetails(usageDetail) + super({}) + + // this.usageType = this.parseUsageType() + // this.seriesName = this.getSeriesFromInstanceType() + // this.vCpuHours = this.usageAmount * this.getVCpus() + // this.gpuHours = this.usageAmount * this.getGpus() + // this.region = this.getRegionFromResourceLocation() + + this.tags = {} + + const tagNames = configLoader()?.AZURE?.RESOURCE_TAG_NAMES ?? [] + + for (const resourceTagName of tagNames) { + if (usageDetail?.tags?.[resourceTagName]) { + this.tags[resourceTagName] = usageDetail.tags[resourceTagName] + } + } + + // if (tagNames.includes(RESOURCE_GROUP_TAG_NAME)) { + // this.tags.resourceGroup = usageDetail.properties.resourceGroup + // } + } +} diff --git a/packages/ali/src/lib/AliCostAndUsageService.ts b/packages/ali/src/lib/AliCostAndUsageService.ts index dfce2b70f..b1c44da48 100644 --- a/packages/ali/src/lib/AliCostAndUsageService.ts +++ b/packages/ali/src/lib/AliCostAndUsageService.ts @@ -23,6 +23,7 @@ import * as $OpenApi from '@alicloud/openapi-client' import * as $BssOpenApi20171214 from '@alicloud/bssopenapi20171214' import moment from 'moment/moment' import * as $Util from '@alicloud/tea-util' +import AliCalculateRow from './AliCalculateRow' export default class AliCostAndUsageService { private readonly logger: Logger @@ -59,6 +60,8 @@ export default class AliCostAndUsageService { aliConfig.authentication.accessKeySecret, ) if (response.body.data.totalCount <= 0) break + const row = new AliCalculateRow(response) + this.logger.info(row.serviceName) response.body.data.items.forEach((cur) => { serviceEstimates.push({ cloudProvider: 'ALI', diff --git a/packages/ali/src/lib/AliTypes.ts b/packages/ali/src/lib/AliTypes.ts new file mode 100644 index 000000000..1a7ffce6f --- /dev/null +++ b/packages/ali/src/lib/AliTypes.ts @@ -0,0 +1,3 @@ +/* + * © 2023 Thoughtworks, Inc. + */ diff --git a/yarn.lock b/yarn.lock index aa40c54eb..dc6977ecf 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12,6 +12,108 @@ __metadata: languageName: node linkType: hard +"@alicloud/bssopenapi20171214@npm:^1.0.12": + version: 1.0.13 + resolution: "@alicloud/bssopenapi20171214@npm:1.0.13" + dependencies: + "@alicloud/endpoint-util": ^0.0.1 + "@alicloud/openapi-client": ^0.4.4 + "@alicloud/openapi-util": ^0.3.1 + "@alicloud/tea-typescript": ^1.7.1 + "@alicloud/tea-util": ^1.4.5 + checksum: fd8f76937e4cc909b962f41c6b3d4b748246719ba72984d1aa37672d2f8c028240e253c57132498e3885b9ca546559c16960b2197e1b77bead992865aeca467b + languageName: node + linkType: hard + +"@alicloud/credentials@npm:^2": + version: 2.2.6 + resolution: "@alicloud/credentials@npm:2.2.6" + dependencies: + "@alicloud/tea-typescript": ^1.5.3 + httpx: ^2.2.0 + ini: ^1.3.5 + kitx: ^2.0.0 + checksum: cf13701bca30b0978f70b68cbd24df21e3d71bc27c3b9818aab6102ab1367b9ed43ce5c7fb8bae3ac5a2524d960236b5e247a6a5c24944e265ab802706120766 + languageName: node + linkType: hard + +"@alicloud/endpoint-util@npm:^0.0.1": + version: 0.0.1 + resolution: "@alicloud/endpoint-util@npm:0.0.1" + dependencies: + "@alicloud/tea-typescript": ^1.5.1 + kitx: ^2.0.0 + checksum: fe570e59813f0c5fec4eacd9a8d5c26f282f2af46b96b54d74c8320289d8168e84aba19b515bacec60217256e2d544a65006b6c6cb54b84cc9d316f6cdaeca53 + languageName: node + linkType: hard + +"@alicloud/gateway-spi@npm:^0.0.8": + version: 0.0.8 + resolution: "@alicloud/gateway-spi@npm:0.0.8" + dependencies: + "@alicloud/credentials": ^2 + "@alicloud/tea-typescript": ^1.7.1 + checksum: e3726191a0de75608632616b90e9b18dccec9eeb6752ae0eaddeeba391922aed566c2a5851922667a5bbf7158db68ed9c0fb026517dcc1038cf9ca457f8357b1 + languageName: node + linkType: hard + +"@alicloud/openapi-client@npm:^0.4.4": + version: 0.4.5 + resolution: "@alicloud/openapi-client@npm:0.4.5" + dependencies: + "@alicloud/credentials": ^2 + "@alicloud/gateway-spi": ^0.0.8 + "@alicloud/openapi-util": ^0.3.1 + "@alicloud/tea-typescript": ^1.7.1 + "@alicloud/tea-util": ^1.4.5 + "@alicloud/tea-xml": 0.0.2 + checksum: 3d6c57290c2dda54d2da9d3d95a8325ddd407746e13eba500e36646aee7b35e933dd74bdbd1846e8728f8822d922fd8e2440adf0cfdc7e70c5cfe5716a9fd15b + languageName: node + linkType: hard + +"@alicloud/openapi-util@npm:^0.3.1": + version: 0.3.1 + resolution: "@alicloud/openapi-util@npm:0.3.1" + dependencies: + "@alicloud/tea-typescript": ^1.7.1 + "@alicloud/tea-util": ^1.3.0 + kitx: ^2.1.0 + sm3: ^1.0.3 + checksum: a8a9a7c6b056df0783b9eaf202ef6bcbc093f06fe789ef03eae2e53d9c2549b18383afe07026982f83e6d97b2b2c8b4cf14f1a35698f7a35ddbb5204ed2c7e34 + languageName: node + linkType: hard + +"@alicloud/tea-typescript@npm:^1, @alicloud/tea-typescript@npm:^1.5.1, @alicloud/tea-typescript@npm:^1.5.3, @alicloud/tea-typescript@npm:^1.7.1": + version: 1.8.0 + resolution: "@alicloud/tea-typescript@npm:1.8.0" + dependencies: + "@types/node": ^12.0.2 + httpx: ^2.2.6 + checksum: d67182b5cd608f0b5b14c7c4d9ded807be87ddd7f43d300a7d05af543fe7f9b1423f2288af8c773e9959184a4df01f22232b3c64ac335bea638a142f8260df64 + languageName: node + linkType: hard + +"@alicloud/tea-util@npm:^1.3.0, @alicloud/tea-util@npm:^1.4.5": + version: 1.4.5 + resolution: "@alicloud/tea-util@npm:1.4.5" + dependencies: + "@alicloud/tea-typescript": ^1.5.1 + kitx: ^2.0.0 + checksum: 29c01e87c5387d8bdb39a0cd68823849b5b44bf01693d687b0e07e9021c174a702d595c4d2f6b9a049e743715c6b1cf60abeb04a55043d4f0634fd61bcf58326 + languageName: node + linkType: hard + +"@alicloud/tea-xml@npm:0.0.2": + version: 0.0.2 + resolution: "@alicloud/tea-xml@npm:0.0.2" + dependencies: + "@alicloud/tea-typescript": ^1 + "@types/xml2js": ^0.4.5 + xml2js: ^0.4.22 + checksum: 51aceea057e559e044e13a0956917af8a59bdc90a7cc7ed289c175d9129899f5b170c0b76cb514dce56043e7d3c929da968f702ab1f32faf5ed2f32517bb027f + languageName: node + linkType: hard + "@apideck/better-ajv-errors@npm:^0.3.1": version: 0.3.6 resolution: "@apideck/better-ajv-errors@npm:0.3.6" @@ -2208,6 +2310,7 @@ __metadata: version: 0.0.0-use.local resolution: "@cloud-carbon-footprint/ali@workspace:packages/ali" dependencies: + "@alicloud/bssopenapi20171214": ^1.0.12 "@cloud-carbon-footprint/common": ^1.9.0 "@cloud-carbon-footprint/core": ^0.17.0 "@google-cloud/iam-credentials": ^1.1.1 @@ -6126,13 +6229,20 @@ __metadata: languageName: node linkType: hard -"@types/node@npm:^12.20.10, @types/node@npm:^12.7.1": +"@types/node@npm:^12.0.2, @types/node@npm:^12.20.10, @types/node@npm:^12.7.1": version: 12.20.55 resolution: "@types/node@npm:12.20.55" checksum: e4f86785f4092706e0d3b0edff8dca5a13b45627e4b36700acd8dfe6ad53db71928c8dee914d4276c7fd3b6ccd829aa919811c9eb708a2c8e4c6eb3701178c37 languageName: node linkType: hard +"@types/node@npm:^14": + version: 14.18.38 + resolution: "@types/node@npm:14.18.38" + checksum: 4f580c5e0f124f44fa6f5d9923bf8fd98f644caabee6171c2dac48160062a91de99f2edd5db5ec59e93fe61f85dd4111df78c9d4f341f3aafca719929362303f + languageName: node + linkType: hard + "@types/node@npm:^17.0.8": version: 17.0.45 resolution: "@types/node@npm:17.0.45" @@ -6479,6 +6589,15 @@ __metadata: languageName: node linkType: hard +"@types/xml2js@npm:^0.4.5": + version: 0.4.11 + resolution: "@types/xml2js@npm:0.4.11" + dependencies: + "@types/node": "*" + checksum: d0e7d2a8c7e0a53147c777a6756bdea0eb26a3de0d3c1f42c17e63332e4e2dc532f687c19ca26a0518336f5de60e1ec4345d7ef71e7ba0656fcf3390c9388eb5 + languageName: node + linkType: hard + "@types/yargs-parser@npm:*": version: 21.0.0 resolution: "@types/yargs-parser@npm:21.0.0" @@ -13092,6 +13211,16 @@ __metadata: languageName: node linkType: hard +"httpx@npm:^2.2.0, httpx@npm:^2.2.6": + version: 2.2.7 + resolution: "httpx@npm:2.2.7" + dependencies: + "@types/node": ^14 + debug: ^4.1.1 + checksum: 3fd62e94dd2dbef48ffdbfd30962e7b258a1638f41f0fbdc07e245282e263fee1ce1f4f3fa6fd66fcee30af5f2fa7a8b0cf4ce97210f92094b68ebc65e9e728e + languageName: node + linkType: hard + "human-id@npm:^1.0.2": version: 1.0.2 resolution: "human-id@npm:1.0.2" @@ -20965,6 +21094,13 @@ __metadata: languageName: node linkType: hard +"sm3@npm:^1.0.3": + version: 1.0.3 + resolution: "sm3@npm:1.0.3" + checksum: d476968485c71e7f489532c0c49e03f3877fcc05406c87a1a2ecc9c23383cc36a2d1e7f2254c0dac473344445e134ed7aaca7489fa6f628a92fd1a706321ca3e + languageName: node + linkType: hard + "smart-buffer@npm:^4.2.0": version: 4.2.0 resolution: "smart-buffer@npm:4.2.0" @@ -24241,7 +24377,7 @@ __metadata: languageName: node linkType: hard -"xml2js@npm:^0.4.19": +"xml2js@npm:^0.4.19, xml2js@npm:^0.4.22": version: 0.4.23 resolution: "xml2js@npm:0.4.23" dependencies: From c8bd11cdf963bb2240b04744212dd01058f8ee11 Mon Sep 17 00:00:00 2001 From: jianglinwang Date: Wed, 15 Mar 2023 17:43:53 +0800 Subject: [PATCH 089/251] add ali calculate compute usage logic. --- .talismanrc | 4 + .../ali/src/__tests__/AliCalculateRow.test.ts | 23 ++++++ packages/ali/src/lib/AliCalculateRow.ts | 53 ++++++++----- .../ali/src/lib/AliCostAndUsageService.ts | 76 +++++++++++++++---- packages/ali/src/lib/AliTypes.ts | 8 ++ packages/client/src/Config.ts | 2 +- .../CarbonIntensityMap/CarbonIntensityMap.tsx | 4 +- 7 files changed, 136 insertions(+), 34 deletions(-) create mode 100644 packages/ali/src/__tests__/AliCalculateRow.test.ts diff --git a/.talismanrc b/.talismanrc index bd35f7729..bc390d5f1 100644 --- a/.talismanrc +++ b/.talismanrc @@ -425,6 +425,10 @@ fileignoreconfig: checksum: 45284a826ebfb0c0f0b32ac161f5cab278b4f0516230f3dfbaf1990a764057d2 - filename: wn-parrots-hug.md checksum: 1f12bbb3fcb87debd6463cc3d5eb46ff5848d21b492993f92bf408dcf6007b94 +- filename: packages/client/src/Config.ts + checksum: e095500f4f411c1921612c55d3ed536f5fd612a6d5072bd2b32c34e54b404ca7 +- filename: packages/ali/src/lib/AliCalculateRow.ts + checksum: d02de37f943221c994878ebf1113850a070d67892cf56a12706b5034664618ec scopeconfig: - scope: node version: "1.0" diff --git a/packages/ali/src/__tests__/AliCalculateRow.test.ts b/packages/ali/src/__tests__/AliCalculateRow.test.ts new file mode 100644 index 000000000..112747ad0 --- /dev/null +++ b/packages/ali/src/__tests__/AliCalculateRow.test.ts @@ -0,0 +1,23 @@ +/* + * © 2023 Thoughtworks, Inc. + */ + +import AliCalculateRow from '../lib/AliCalculateRow' +import { DescribeInstanceBillResponseBodyDataItems } from '@alicloud/bssopenapi20171214' + +describe('Ali Calculate Row', () => { + it('gets results from getDataForRegions function', async () => { + const item = new DescribeInstanceBillResponseBodyDataItems() + item.instanceConfig = + 'I/O 优化实例:I/O 优化实例;操作系统位数:64位;实例规格族:ecs.s6;systemdisk_delete_with_instance:true;实例规格:1核 4GB;操作系统的类型:Linux;体检服务:是;地域:成都region;可用区:可用区A;CPU:1核;系统盘种类:增强型SSD云盘;虚拟交换机:vsw-2vcwp5da90fk0ytcgrn5r;网络类型:专有网络;系统盘大小:50GB;系统盘性能level:PL0;实例系列:系列 V;操作系统:m-2vcg1zc8duf3fwq39n9q;内存:4GBMB;是否是按流量计费:按流量计费;挂载点:/dev/xvda;带宽:10240Kbps;管家服务:是' + item.region = '西南1(成都)' + item.instanceSpec = 'ecs.s6-c1m4.small' + item.productCode = 'ecs' + const row = new AliCalculateRow(item) + console.log(row) + expect(row.vCpuHours).toEqual(720) + expect(row.region).toEqual('西南1(成都)') + expect(row.seriesName).toEqual('ecs.s6-c1m4.small') + expect(row.serviceName).toEqual('ecs') + }) +}) diff --git a/packages/ali/src/lib/AliCalculateRow.ts b/packages/ali/src/lib/AliCalculateRow.ts index 5911a62f0..0f1f4e1ea 100644 --- a/packages/ali/src/lib/AliCalculateRow.ts +++ b/packages/ali/src/lib/AliCalculateRow.ts @@ -3,32 +3,51 @@ */ import { BillingDataRow } from '@cloud-carbon-footprint/core' -import { configLoader } from '@cloud-carbon-footprint/common' -import { DescribeInstanceBillResponse } from '@alicloud/bssopenapi20171214' +import { DescribeInstanceBillResponseBodyDataItems } from '@alicloud/bssopenapi20171214/src/client' export default class AliCalculateRow extends BillingDataRow { - constructor(usageDetail: DescribeInstanceBillResponse) { + constructor(usageDetail: DescribeInstanceBillResponseBodyDataItems) { // const consumptionDetails = getConsumptionDetails(usageDetail) super({}) // this.usageType = this.parseUsageType() - // this.seriesName = this.getSeriesFromInstanceType() - // this.vCpuHours = this.usageAmount * this.getVCpus() - // this.gpuHours = this.usageAmount * this.getGpus() - // this.region = this.getRegionFromResourceLocation() + this.serviceName = usageDetail.productCode + this.seriesName = this.getSeriesName(usageDetail) + this.vCpuHours = this.getVCpuHours(usageDetail) + this.gpuHours = this.getGpuHours(usageDetail) + this.region = usageDetail.region + } + + private getSeriesName( + usageDetail: DescribeInstanceBillResponseBodyDataItems, + ) { + return usageDetail.instanceSpec + } - this.tags = {} + private getVCpuHours(usageDetail: DescribeInstanceBillResponseBodyDataItems) { + const instanceConfig = usageDetail.instanceConfig + return ( + this.getUsage() * + parseInt(this.getJsonValue('CPU', instanceConfig).split('核')[0]) + ) + } - const tagNames = configLoader()?.AZURE?.RESOURCE_TAG_NAMES ?? [] + private getGpuHours(usageDetail: DescribeInstanceBillResponseBodyDataItems) { + const instanceConfig = usageDetail.instanceConfig + const gpuStr = this.getJsonValue('GPU', instanceConfig) + return ( + this.getUsage() * parseInt(gpuStr == null ? '0' : gpuStr.split('核')[0]) + ) + } - for (const resourceTagName of tagNames) { - if (usageDetail?.tags?.[resourceTagName]) { - this.tags[resourceTagName] = usageDetail.tags[resourceTagName] - } - } + private getUsage() { + return 30 * 24 + } - // if (tagNames.includes(RESOURCE_GROUP_TAG_NAME)) { - // this.tags.resourceGroup = usageDetail.properties.resourceGroup - // } + public getJsonValue(key: string, jsonStr: string): string { + const str = `(?<=(${key}):).*?(?=(;|$))` + const regExp = new RegExp(str, 'g') + const result = jsonStr.match(regExp) + return result && result.length != 0 ? result[0] : null } } diff --git a/packages/ali/src/lib/AliCostAndUsageService.ts b/packages/ali/src/lib/AliCostAndUsageService.ts index b1c44da48..78709561f 100644 --- a/packages/ali/src/lib/AliCostAndUsageService.ts +++ b/packages/ali/src/lib/AliCostAndUsageService.ts @@ -3,27 +3,27 @@ */ import { + CloudConstants, + CloudConstantsEmissionsFactors, + COMPUTE_PROCESSOR_TYPES, ComputeEstimator, + ComputeUsage, EmbodiedEmissionsEstimator, + FootprintEstimate, MemoryEstimator, NetworkingEstimator, StorageEstimator, UnknownEstimator, } from '@cloud-carbon-footprint/core' import { ServiceWrapper } from '@cloud-carbon-footprint/aws' -import { - configLoader, - EstimationResult, - GroupBy, - Logger, - ServiceData, -} from '@cloud-carbon-footprint/common' -import BssOpenApi20171214 from '@alicloud/bssopenapi20171214' +import { configLoader, EstimationResult, GroupBy, Logger, ServiceData } from '@cloud-carbon-footprint/common' +import BssOpenApi20171214, * as $BssOpenApi20171214 from '@alicloud/bssopenapi20171214' import * as $OpenApi from '@alicloud/openapi-client' -import * as $BssOpenApi20171214 from '@alicloud/bssopenapi20171214' import moment from 'moment/moment' import * as $Util from '@alicloud/tea-util' import AliCalculateRow from './AliCalculateRow' +import { ALI_CLOUD_CONSTANTS, ALI_EMISSIONS_FACTORS_METRIC_TON_PER_KWH } from '../domain' +import { INSTANCE_TYPE_COMPUTE_PROCESSOR_MAPPING } from './AliTypes' export default class AliCostAndUsageService { private readonly logger: Logger @@ -52,7 +52,8 @@ export default class AliCostAndUsageService { const result: EstimationResult[] = [] const serviceEstimates: ServiceData[] = [] const aliConfig = configLoader().ALI - + const emissionsFactors: CloudConstantsEmissionsFactors = + ALI_EMISSIONS_FACTORS_METRIC_TON_PER_KWH for (const date of this.getDates(start, end)) { const response = await this.getUsage( date, @@ -60,17 +61,23 @@ export default class AliCostAndUsageService { aliConfig.authentication.accessKeySecret, ) if (response.body.data.totalCount <= 0) break - const row = new AliCalculateRow(response) - this.logger.info(row.serviceName) response.body.data.items.forEach((cur) => { + const row = new AliCalculateRow(cur) + const pue = ALI_CLOUD_CONSTANTS.getPUE() + const computeFootprintEstimate = this.getComputeFootprintEstimate( + row, + pue, + emissionsFactors, + ) + serviceEstimates.push({ cloudProvider: 'ALI', accountName: cur.billAccountName, serviceName: cur.nickName, accountId: cur.billAccountID, // todo calculate - kilowattHours: 0, - co2e: 0, + kilowattHours: computeFootprintEstimate.kilowattHours, + co2e: computeFootprintEstimate.co2e, cost: 0, // todo chinese to english? region: cur.region, @@ -124,4 +131,45 @@ export default class AliCostAndUsageService { runtime, ) } + + private getComputeFootprintEstimate( + row: AliCalculateRow, + pue: number, + emissionsFactors: CloudConstantsEmissionsFactors, + ): FootprintEstimate { + const computeUsage: ComputeUsage = { + timestamp: row.timestamp, + cpuUtilizationAverage: ALI_CLOUD_CONSTANTS.AVG_CPU_UTILIZATION_2020, + vCpuHours: row.vCpuHours, + usesAverageCPUConstant: true, + } + const processors = this.getComputeProcessorsFromAliInstanceType( + row.instanceType, + ) + const cpuComputeConstants: CloudConstants = { + minWatts: ALI_CLOUD_CONSTANTS.getMinWatts(processors), + maxWatts: ALI_CLOUD_CONSTANTS.getMaxWatts(processors), + powerUsageEffectiveness: pue, + replicationFactor: this.getReplicationFactor(row), + } + return this.computeEstimator.estimate( + [computeUsage], + row.region, + emissionsFactors, + cpuComputeConstants, + )[0] + } + + private getComputeProcessorsFromAliInstanceType(instanceType: string) { + return ( + INSTANCE_TYPE_COMPUTE_PROCESSOR_MAPPING[instanceType] || [ + COMPUTE_PROCESSOR_TYPES.UNKNOWN, + ] + ) + } + + private getReplicationFactor(row: AliCalculateRow) { + this.logger.info('getReplicationFactor' + row) + return 0 + } } diff --git a/packages/ali/src/lib/AliTypes.ts b/packages/ali/src/lib/AliTypes.ts index 1a7ffce6f..ce59ce3f7 100644 --- a/packages/ali/src/lib/AliTypes.ts +++ b/packages/ali/src/lib/AliTypes.ts @@ -1,3 +1,11 @@ /* * © 2023 Thoughtworks, Inc. */ + +import { COMPUTE_PROCESSOR_TYPES } from '@cloud-carbon-footprint/core' + +export const INSTANCE_TYPE_COMPUTE_PROCESSOR_MAPPING: { + [series: string]: string[] +} = { + 'ecs.s6': [COMPUTE_PROCESSOR_TYPES.CASCADE_LAKE], +} diff --git a/packages/client/src/Config.ts b/packages/client/src/Config.ts index adab56bc2..a24e65c4e 100644 --- a/packages/client/src/Config.ts +++ b/packages/client/src/Config.ts @@ -46,7 +46,7 @@ const appConfig: ClientConfig = { { key: 'aws', name: 'AWS' }, { key: 'gcp', name: 'GCP' }, { key: 'azure', name: 'Azure' }, - { key: 'ali', name: 'ALI' }, + { key: 'ali', name: 'AliCloud' }, ], PREVIOUS_YEAR_OF_USAGE: previousYearOfUsage, DATE_RANGE: { diff --git a/packages/client/src/pages/EmissionsMetricsPage/CarbonIntensityMap/CarbonIntensityMap.tsx b/packages/client/src/pages/EmissionsMetricsPage/CarbonIntensityMap/CarbonIntensityMap.tsx index 112a73da8..aacebbe54 100644 --- a/packages/client/src/pages/EmissionsMetricsPage/CarbonIntensityMap/CarbonIntensityMap.tsx +++ b/packages/client/src/pages/EmissionsMetricsPage/CarbonIntensityMap/CarbonIntensityMap.tsx @@ -11,7 +11,7 @@ import GCPMap from './GCPMap.png' import AzureMap from './AzureMap.png' import useStyles from './carbonIntensityStyles' -type CloudProvider = 'AWS' | 'GCP' | 'Azure' | 'ALI' +type CloudProvider = 'AWS' | 'GCP' | 'Azure' | 'AliCloud' type IntensityMaps = { [provider in CloudProvider]: React.ReactNode @@ -25,7 +25,7 @@ const CarbonIntensityMap = (): ReactElement => { AWS: AWSMap, GCP: GCPMap, Azure: AzureMap, - ALI: AWSMap, + AliCloud: AWSMap, } const handleChange = (event: React.ChangeEvent<{ value: string }>) => { From 4194a6eae4f474fe5aa01f9777205653103a6fe1 Mon Sep 17 00:00:00 2001 From: dean Date: Thu, 16 Mar 2023 11:05:22 +0800 Subject: [PATCH 090/251] get data from month --- packages/ali/src/lib/AliCostAndUsageService.ts | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/packages/ali/src/lib/AliCostAndUsageService.ts b/packages/ali/src/lib/AliCostAndUsageService.ts index 78709561f..19a8429a1 100644 --- a/packages/ali/src/lib/AliCostAndUsageService.ts +++ b/packages/ali/src/lib/AliCostAndUsageService.ts @@ -16,13 +16,22 @@ import { UnknownEstimator, } from '@cloud-carbon-footprint/core' import { ServiceWrapper } from '@cloud-carbon-footprint/aws' -import { configLoader, EstimationResult, GroupBy, Logger, ServiceData } from '@cloud-carbon-footprint/common' +import { + configLoader, + EstimationResult, + GroupBy, + Logger, + ServiceData, +} from '@cloud-carbon-footprint/common' import BssOpenApi20171214, * as $BssOpenApi20171214 from '@alicloud/bssopenapi20171214' import * as $OpenApi from '@alicloud/openapi-client' import moment from 'moment/moment' import * as $Util from '@alicloud/tea-util' import AliCalculateRow from './AliCalculateRow' -import { ALI_CLOUD_CONSTANTS, ALI_EMISSIONS_FACTORS_METRIC_TON_PER_KWH } from '../domain' +import { + ALI_CLOUD_CONSTANTS, + ALI_EMISSIONS_FACTORS_METRIC_TON_PER_KWH, +} from '../domain' import { INSTANCE_TYPE_COMPUTE_PROCESSOR_MAPPING } from './AliTypes' export default class AliCostAndUsageService { @@ -98,7 +107,7 @@ export default class AliCostAndUsageService { const currentDate = new Date(startDate) while (currentDate <= endDate) { dates.push(new Date(currentDate)) - currentDate.setDate(currentDate.getDate() + 1) + currentDate.setMonth(currentDate.getMonth() + 1) } return dates } From 3cf6328c8853740a33282893d856003cddf5250c Mon Sep 17 00:00:00 2001 From: jianglinwang Date: Thu, 16 Mar 2023 11:09:06 +0800 Subject: [PATCH 091/251] set data for ali cloud. --- .talismanrc | 2 ++ .../domain/AliFootprintEstimationConstants.ts | 20 +++-------- packages/ali/src/lib/AliCalculateRow.ts | 34 ++++++++++++++----- .../ali/src/lib/AliCostAndUsageService.ts | 28 +++++++-------- packages/ali/src/lib/ReplicationFactors.ts | 22 ++++++++++++ 5 files changed, 68 insertions(+), 38 deletions(-) create mode 100644 packages/ali/src/lib/ReplicationFactors.ts diff --git a/.talismanrc b/.talismanrc index bc390d5f1..62c4df38d 100644 --- a/.talismanrc +++ b/.talismanrc @@ -429,6 +429,8 @@ fileignoreconfig: checksum: e095500f4f411c1921612c55d3ed536f5fd612a6d5072bd2b32c34e54b404ca7 - filename: packages/ali/src/lib/AliCalculateRow.ts checksum: d02de37f943221c994878ebf1113850a070d67892cf56a12706b5034664618ec +- filename: packages/ali/src/lib/AliCalculateRow.ts + checksum: 77997e1e88c3fe9bbafbda63d4c1c9b83df439417664cf0d080488b01c8c0c51 scopeconfig: - scope: node version: "1.0" diff --git a/packages/ali/src/domain/AliFootprintEstimationConstants.ts b/packages/ali/src/domain/AliFootprintEstimationConstants.ts index 76c2c6428..a7eb4467b 100644 --- a/packages/ali/src/domain/AliFootprintEstimationConstants.ts +++ b/packages/ali/src/domain/AliFootprintEstimationConstants.ts @@ -124,21 +124,7 @@ export const ALI_CLOUD_CONSTANTS: CloudConstantsByProvider = { }, AVG_CPU_UTILIZATION_2020: 50, REPLICATION_FACTORS: { - S3: 6, - S3_ONE_ZONE_REDUCED_REDUNDANCY: 2, - EC2_EBS_VOLUME: 2, - EC2_EBS_SNAPSHOT: 3, - EFS: 3, - EFS_ONE_ZONE: 2, - RDS_BACKUP: 3, - RDS_AURORA: 6, - RDS_MULTI_AZ: 2, - DOCUMENT_DB_BACKUP: 3, - DOCUMENT_DB_STORAGE: 2, - DYNAMO_DB: 2, - ECR_STORAGE: 3, - DOCUMENT_ELASTICACHE_BACKUP: 3, - SIMPLE_DB: 2, + ECS: 6, DEFAULT: 1, }, KILOWATT_HOURS_BY_SERVICE_AND_USAGE_UNIT: { @@ -149,4 +135,6 @@ export const ALI_CLOUD_CONSTANTS: CloudConstantsByProvider = { } export const ALI_EMISSIONS_FACTORS_METRIC_TON_PER_KWH: CloudConstantsEmissionsFactors = - {} + { + ['Unknown']: 0.0003512799615, // Average of above regions + } diff --git a/packages/ali/src/lib/AliCalculateRow.ts b/packages/ali/src/lib/AliCalculateRow.ts index 0f1f4e1ea..4e44ad6f8 100644 --- a/packages/ali/src/lib/AliCalculateRow.ts +++ b/packages/ali/src/lib/AliCalculateRow.ts @@ -4,6 +4,7 @@ import { BillingDataRow } from '@cloud-carbon-footprint/core' import { DescribeInstanceBillResponseBodyDataItems } from '@alicloud/bssopenapi20171214/src/client' +import { ALI_REPLICATION_FACTORS_FOR_SERVICES } from './ReplicationFactors' export default class AliCalculateRow extends BillingDataRow { constructor(usageDetail: DescribeInstanceBillResponseBodyDataItems) { @@ -16,6 +17,10 @@ export default class AliCalculateRow extends BillingDataRow { this.vCpuHours = this.getVCpuHours(usageDetail) this.gpuHours = this.getGpuHours(usageDetail) this.region = usageDetail.region + this.replicationFactor = this.getReplicationFactor() + this.cost = usageDetail.cashAmount + this.accountName = usageDetail.billAccountName + this.accountId = usageDetail.billAccountID } private getSeriesName( @@ -26,18 +31,20 @@ export default class AliCalculateRow extends BillingDataRow { private getVCpuHours(usageDetail: DescribeInstanceBillResponseBodyDataItems) { const instanceConfig = usageDetail.instanceConfig - return ( - this.getUsage() * - parseInt(this.getJsonValue('CPU', instanceConfig).split('核')[0]) - ) + return this.getUsage() * this.parseCpuAndGpu(instanceConfig, 'CPU') } private getGpuHours(usageDetail: DescribeInstanceBillResponseBodyDataItems) { const instanceConfig = usageDetail.instanceConfig - const gpuStr = this.getJsonValue('GPU', instanceConfig) - return ( - this.getUsage() * parseInt(gpuStr == null ? '0' : gpuStr.split('核')[0]) - ) + return this.getUsage() * this.parseCpuAndGpu(instanceConfig, 'GPU') + } + + private parseCpuAndGpu(instanceConfig: string, key: string): number { + const keyValue = this.getJsonValue(key, instanceConfig) + if (keyValue == null) { + return 1 + } + return parseInt(keyValue.split('核')[0]) } private getUsage() { @@ -50,4 +57,15 @@ export default class AliCalculateRow extends BillingDataRow { const result = jsonStr.match(regExp) return result && result.length != 0 ? result[0] : null } + + private getReplicationFactor(): number { + return ( + (ALI_REPLICATION_FACTORS_FOR_SERVICES[this.serviceName] && + ALI_REPLICATION_FACTORS_FOR_SERVICES[this.serviceName]( + this.usageType, + this.region, + )) || + ALI_REPLICATION_FACTORS_FOR_SERVICES.DEFAULT() + ) + } } diff --git a/packages/ali/src/lib/AliCostAndUsageService.ts b/packages/ali/src/lib/AliCostAndUsageService.ts index 19a8429a1..666b3f708 100644 --- a/packages/ali/src/lib/AliCostAndUsageService.ts +++ b/packages/ali/src/lib/AliCostAndUsageService.ts @@ -69,6 +69,7 @@ export default class AliCostAndUsageService { aliConfig.authentication.accessKeyId, aliConfig.authentication.accessKeySecret, ) + this.logger.info('response:' + JSON.stringify(response)) if (response.body.data.totalCount <= 0) break response.body.data.items.forEach((cur) => { const row = new AliCalculateRow(cur) @@ -79,17 +80,16 @@ export default class AliCostAndUsageService { emissionsFactors, ) + this.logger.info('row:' + JSON.stringify(row)) serviceEstimates.push({ - cloudProvider: 'ALI', - accountName: cur.billAccountName, - serviceName: cur.nickName, - accountId: cur.billAccountID, - // todo calculate + cloudProvider: 'AliCloud', + accountName: row.accountName, + serviceName: row.serviceName, + accountId: row.accountId, kilowattHours: computeFootprintEstimate.kilowattHours, co2e: computeFootprintEstimate.co2e, - cost: 0, - // todo chinese to english? - region: cur.region, + cost: row.cost, + region: row.region, usesAverageCPUConstant: false, }) }) @@ -159,8 +159,13 @@ export default class AliCostAndUsageService { minWatts: ALI_CLOUD_CONSTANTS.getMinWatts(processors), maxWatts: ALI_CLOUD_CONSTANTS.getMaxWatts(processors), powerUsageEffectiveness: pue, - replicationFactor: this.getReplicationFactor(row), + replicationFactor: row.replicationFactor, } + this.logger.info( + 'cpuComputeConstants:' + JSON.stringify(cpuComputeConstants), + ) + this.logger.info('computeUsage:' + JSON.stringify(computeUsage)) + this.logger.info('processors:' + JSON.stringify(processors)) return this.computeEstimator.estimate( [computeUsage], row.region, @@ -176,9 +181,4 @@ export default class AliCostAndUsageService { ] ) } - - private getReplicationFactor(row: AliCalculateRow) { - this.logger.info('getReplicationFactor' + row) - return 0 - } } diff --git a/packages/ali/src/lib/ReplicationFactors.ts b/packages/ali/src/lib/ReplicationFactors.ts new file mode 100644 index 000000000..a647270bd --- /dev/null +++ b/packages/ali/src/lib/ReplicationFactors.ts @@ -0,0 +1,22 @@ +/* + * © 2023 Thoughtworks, Inc. + */ + +import { ReplicationFactorsForService } from '@cloud-carbon-footprint/core' +import { ALI_CLOUD_CONSTANTS } from '../domain' + +const { REPLICATION_FACTORS } = ALI_CLOUD_CONSTANTS + +enum SERVICES { + ECS = 'ecs', +} + +export const ALI_REPLICATION_FACTORS_FOR_SERVICES: ReplicationFactorsForService = + { + [SERVICES.ECS]: (): number => { + return REPLICATION_FACTORS.DEFAULT + }, + DEFAULT() { + return REPLICATION_FACTORS.DEFAULT + }, + } From 51838c51c8a4172426b1ea146e0d123205be3fb6 Mon Sep 17 00:00:00 2001 From: dean Date: Thu, 16 Mar 2023 11:26:29 +0800 Subject: [PATCH 092/251] fix serviceEstimates init in every date --- packages/ali/src/lib/AliCostAndUsageService.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ali/src/lib/AliCostAndUsageService.ts b/packages/ali/src/lib/AliCostAndUsageService.ts index 666b3f708..8d5415cbd 100644 --- a/packages/ali/src/lib/AliCostAndUsageService.ts +++ b/packages/ali/src/lib/AliCostAndUsageService.ts @@ -59,11 +59,11 @@ export default class AliCostAndUsageService { `startDate: ${start}, endDate: ${end}, grouping: ${grouping}`, ) const result: EstimationResult[] = [] - const serviceEstimates: ServiceData[] = [] const aliConfig = configLoader().ALI const emissionsFactors: CloudConstantsEmissionsFactors = ALI_EMISSIONS_FACTORS_METRIC_TON_PER_KWH for (const date of this.getDates(start, end)) { + const serviceEstimates: ServiceData[] = [] const response = await this.getUsage( date, aliConfig.authentication.accessKeyId, From cbde2c8de52073b03744fdb30ccc3f23a2c54331 Mon Sep 17 00:00:00 2001 From: jianglinwang Date: Thu, 16 Mar 2023 14:49:47 +0800 Subject: [PATCH 093/251] add gpu calculate logic. --- .talismanrc | 4 +- packages/ali/src/application/AliAccount.ts | 4 - .../domain/AliFootprintEstimationConstants.ts | 2 +- packages/ali/src/lib/AliCalculateRow.ts | 17 ++-- .../ali/src/lib/AliCostAndUsageService.ts | 80 ++++++++++++++++--- packages/ali/src/lib/AliTypes.ts | 7 ++ packages/client/src/Config.ts | 2 +- 7 files changed, 87 insertions(+), 29 deletions(-) diff --git a/.talismanrc b/.talismanrc index 62c4df38d..6a8e1947d 100644 --- a/.talismanrc +++ b/.talismanrc @@ -426,9 +426,7 @@ fileignoreconfig: - filename: wn-parrots-hug.md checksum: 1f12bbb3fcb87debd6463cc3d5eb46ff5848d21b492993f92bf408dcf6007b94 - filename: packages/client/src/Config.ts - checksum: e095500f4f411c1921612c55d3ed536f5fd612a6d5072bd2b32c34e54b404ca7 -- filename: packages/ali/src/lib/AliCalculateRow.ts - checksum: d02de37f943221c994878ebf1113850a070d67892cf56a12706b5034664618ec + checksum: a1c04f96a9be8e217e535f25a61a5cae1c40933129a05dac81243bb2caa92c60 - filename: packages/ali/src/lib/AliCalculateRow.ts checksum: 77997e1e88c3fe9bbafbda63d4c1c9b83df439417664cf0d080488b01c8c0c51 scopeconfig: diff --git a/packages/ali/src/application/AliAccount.ts b/packages/ali/src/application/AliAccount.ts index be2e9a6b3..eaaa5b8ce 100644 --- a/packages/ali/src/application/AliAccount.ts +++ b/packages/ali/src/application/AliAccount.ts @@ -44,10 +44,6 @@ export default class AliAccount extends CloudProviderAccount { endDate: Date, grouping: GroupBy, ): Promise { - this.logger.info( - `startDate: ${startDate}, endDate: ${endDate}, grouping: ${grouping}`, - ) - const aliCostAndUsageService = new AliCostAndUsageService( new ComputeEstimator(), new StorageEstimator(ALI_CLOUD_CONSTANTS.SSDCOEFFICIENT), diff --git a/packages/ali/src/domain/AliFootprintEstimationConstants.ts b/packages/ali/src/domain/AliFootprintEstimationConstants.ts index a7eb4467b..463a4802e 100644 --- a/packages/ali/src/domain/AliFootprintEstimationConstants.ts +++ b/packages/ali/src/domain/AliFootprintEstimationConstants.ts @@ -124,7 +124,7 @@ export const ALI_CLOUD_CONSTANTS: CloudConstantsByProvider = { }, AVG_CPU_UTILIZATION_2020: 50, REPLICATION_FACTORS: { - ECS: 6, + ECS: 1, DEFAULT: 1, }, KILOWATT_HOURS_BY_SERVICE_AND_USAGE_UNIT: { diff --git a/packages/ali/src/lib/AliCalculateRow.ts b/packages/ali/src/lib/AliCalculateRow.ts index 4e44ad6f8..8b8db78ad 100644 --- a/packages/ali/src/lib/AliCalculateRow.ts +++ b/packages/ali/src/lib/AliCalculateRow.ts @@ -7,20 +7,23 @@ import { DescribeInstanceBillResponseBodyDataItems } from '@alicloud/bssopenapi2 import { ALI_REPLICATION_FACTORS_FOR_SERVICES } from './ReplicationFactors' export default class AliCalculateRow extends BillingDataRow { + readonly specificationFamily: string constructor(usageDetail: DescribeInstanceBillResponseBodyDataItems) { // const consumptionDetails = getConsumptionDetails(usageDetail) - super({}) + super({ + accountId: usageDetail.billAccountID, + accountName: usageDetail.billAccountName, + cost: usageDetail.cashAmount, + region: usageDetail.region, + serviceName: usageDetail.productCode, + }) // this.usageType = this.parseUsageType() - this.serviceName = usageDetail.productCode this.seriesName = this.getSeriesName(usageDetail) this.vCpuHours = this.getVCpuHours(usageDetail) this.gpuHours = this.getGpuHours(usageDetail) - this.region = usageDetail.region this.replicationFactor = this.getReplicationFactor() - this.cost = usageDetail.cashAmount - this.accountName = usageDetail.billAccountName - this.accountId = usageDetail.billAccountID + this.specificationFamily = this.seriesName.split('.').slice(0, 2).join('.') } private getSeriesName( @@ -42,7 +45,7 @@ export default class AliCalculateRow extends BillingDataRow { private parseCpuAndGpu(instanceConfig: string, key: string): number { const keyValue = this.getJsonValue(key, instanceConfig) if (keyValue == null) { - return 1 + return 0 } return parseInt(keyValue.split('核')[0]) } diff --git a/packages/ali/src/lib/AliCostAndUsageService.ts b/packages/ali/src/lib/AliCostAndUsageService.ts index 8d5415cbd..cad88857d 100644 --- a/packages/ali/src/lib/AliCostAndUsageService.ts +++ b/packages/ali/src/lib/AliCostAndUsageService.ts @@ -15,7 +15,6 @@ import { StorageEstimator, UnknownEstimator, } from '@cloud-carbon-footprint/core' -import { ServiceWrapper } from '@cloud-carbon-footprint/aws' import { configLoader, EstimationResult, @@ -32,7 +31,11 @@ import { ALI_CLOUD_CONSTANTS, ALI_EMISSIONS_FACTORS_METRIC_TON_PER_KWH, } from '../domain' -import { INSTANCE_TYPE_COMPUTE_PROCESSOR_MAPPING } from './AliTypes' +import { + GPU_VIRTUAL_MACHINE_TYPE_PROCESSOR_MAPPING, + INSTANCE_TYPE_COMPUTE_PROCESSOR_MAPPING, +} from './AliTypes' +import { AZURE_CLOUD_CONSTANTS } from '@cloud-carbon-footprint/azure' export default class AliCostAndUsageService { private readonly logger: Logger @@ -45,7 +48,6 @@ export default class AliCostAndUsageService { private readonly memoryEstimator: MemoryEstimator, private readonly unknownEstimator: UnknownEstimator, private readonly embodiedEmissionsEstimator: EmbodiedEmissionsEstimator, - private readonly serviceWrapper?: ServiceWrapper, ) { this.logger = new Logger('AliCostAndUsageService') } @@ -55,9 +57,6 @@ export default class AliCostAndUsageService { end: Date, grouping: GroupBy, ): Promise { - this.logger.info( - `startDate: ${start}, endDate: ${end}, grouping: ${grouping}`, - ) const result: EstimationResult[] = [] const aliConfig = configLoader().ALI const emissionsFactors: CloudConstantsEmissionsFactors = @@ -93,6 +92,7 @@ export default class AliCostAndUsageService { usesAverageCPUConstant: false, }) }) + this.logger.info('serviceEstimates:' + JSON.stringify(serviceEstimates)) result.push({ groupBy: grouping, timestamp: date, @@ -134,6 +134,7 @@ export default class AliCostAndUsageService { new $BssOpenApi20171214.DescribeInstanceBillRequest({ billingCycle: moment(date).format('YYYY-MM'), }) + this.logger.info('request:' + JSON.stringify(describeInstanceBillRequest)) const runtime = new $Util.RuntimeOptions({}) return await client.describeInstanceBillWithOptions( describeInstanceBillRequest, @@ -153,7 +154,7 @@ export default class AliCostAndUsageService { usesAverageCPUConstant: true, } const processors = this.getComputeProcessorsFromAliInstanceType( - row.instanceType, + row.specificationFamily, ) const cpuComputeConstants: CloudConstants = { minWatts: ALI_CLOUD_CONSTANTS.getMinWatts(processors), @@ -161,17 +162,58 @@ export default class AliCostAndUsageService { powerUsageEffectiveness: pue, replicationFactor: row.replicationFactor, } - this.logger.info( - 'cpuComputeConstants:' + JSON.stringify(cpuComputeConstants), - ) - this.logger.info('computeUsage:' + JSON.stringify(computeUsage)) - this.logger.info('processors:' + JSON.stringify(processors)) - return this.computeEstimator.estimate( + const computeFootprintEstimate = this.computeEstimator.estimate( [computeUsage], row.region, emissionsFactors, cpuComputeConstants, )[0] + + if (this.isGpuUsage(row.gpuHours)) { + return this.appendGpuComputeEstimate( + row, + pue, + emissionsFactors, + computeFootprintEstimate, + ) + } + return computeFootprintEstimate + } + + private appendGpuComputeEstimate( + row: AliCalculateRow, + powerUsageEffectiveness: number, + emissionsFactors: CloudConstantsEmissionsFactors, + computeFootprintEstimate: FootprintEstimate, + ) { + const gpuComputeProcessors = + this.getGPUComputeProcessorsFromAliInstanceType(row.specificationFamily) + + const gpuComputeUsage: ComputeUsage = { + timestamp: row.timestamp, + cpuUtilizationAverage: AZURE_CLOUD_CONSTANTS.AVG_CPU_UTILIZATION_2020, + vCpuHours: row.gpuHours, + usesAverageCPUConstant: true, + } + + const gpuComputeConstants: CloudConstants = { + minWatts: AZURE_CLOUD_CONSTANTS.getMinWatts(gpuComputeProcessors), + maxWatts: AZURE_CLOUD_CONSTANTS.getMaxWatts(gpuComputeProcessors), + powerUsageEffectiveness: powerUsageEffectiveness, + replicationFactor: row.replicationFactor, + } + + const gpuComputeFootprintEstimate = this.computeEstimator.estimate( + [gpuComputeUsage], + row.region, + emissionsFactors, + gpuComputeConstants, + )[0] + + computeFootprintEstimate.kilowattHours += + gpuComputeFootprintEstimate.kilowattHours + computeFootprintEstimate.co2e += gpuComputeFootprintEstimate.co2e + return computeFootprintEstimate } private getComputeProcessorsFromAliInstanceType(instanceType: string) { @@ -181,4 +223,16 @@ export default class AliCostAndUsageService { ] ) } + + private isGpuUsage(gpuHours: number) { + return gpuHours != 0 + } + + private getGPUComputeProcessorsFromAliInstanceType(instanceType: string) { + return ( + GPU_VIRTUAL_MACHINE_TYPE_PROCESSOR_MAPPING[instanceType] || [ + COMPUTE_PROCESSOR_TYPES.UNKNOWN, + ] + ) + } } diff --git a/packages/ali/src/lib/AliTypes.ts b/packages/ali/src/lib/AliTypes.ts index ce59ce3f7..6b96155b0 100644 --- a/packages/ali/src/lib/AliTypes.ts +++ b/packages/ali/src/lib/AliTypes.ts @@ -9,3 +9,10 @@ export const INSTANCE_TYPE_COMPUTE_PROCESSOR_MAPPING: { } = { 'ecs.s6': [COMPUTE_PROCESSOR_TYPES.CASCADE_LAKE], } + +export const GPU_VIRTUAL_MACHINE_TYPE_PROCESSOR_MAPPING: { + [series: string]: string[] +} = { + 'ecs.sgn7i': [COMPUTE_PROCESSOR_TYPES.NVIDIA_A10G], + 'ecs.gn7r': [COMPUTE_PROCESSOR_TYPES.NVIDIA_A10G], +} diff --git a/packages/client/src/Config.ts b/packages/client/src/Config.ts index a24e65c4e..80cc36675 100644 --- a/packages/client/src/Config.ts +++ b/packages/client/src/Config.ts @@ -46,7 +46,7 @@ const appConfig: ClientConfig = { { key: 'aws', name: 'AWS' }, { key: 'gcp', name: 'GCP' }, { key: 'azure', name: 'Azure' }, - { key: 'ali', name: 'AliCloud' }, + { key: 'alicloud', name: 'AliCloud' }, ], PREVIOUS_YEAR_OF_USAGE: previousYearOfUsage, DATE_RANGE: { From eb746d6594275572fffaefeeb5de69f3d55f4efd Mon Sep 17 00:00:00 2001 From: jianglinwang Date: Thu, 16 Mar 2023 18:04:38 +0800 Subject: [PATCH 094/251] add ali region and region emission factor. --- .../domain/AliFootprintEstimationConstants.ts | 17 +++++++++++++++-- packages/ali/src/lib/AliRegions.ts | 11 +++++++++++ 2 files changed, 26 insertions(+), 2 deletions(-) create mode 100644 packages/ali/src/lib/AliRegions.ts diff --git a/packages/ali/src/domain/AliFootprintEstimationConstants.ts b/packages/ali/src/domain/AliFootprintEstimationConstants.ts index 463a4802e..5de90387d 100644 --- a/packages/ali/src/domain/AliFootprintEstimationConstants.ts +++ b/packages/ali/src/domain/AliFootprintEstimationConstants.ts @@ -9,6 +9,7 @@ import { getAverage, getWattsByAverageOrMedian, } from '@cloud-carbon-footprint/core' +import { ALI_REGIONS } from '../lib/AliRegions' export const ALI_CLOUD_CONSTANTS: CloudConstantsByProvider = { SSDCOEFFICIENT: 1.2, // watt hours / terabyte hour @@ -119,8 +120,16 @@ export const ALI_CLOUD_CONSTANTS: CloudConstantsByProvider = { NETWORKING_COEFFICIENT: 0.001, // kWh / Gb MEMORY_COEFFICIENT: 0.000392, // kWh / Gb PUE_AVG: 1.3, - getPUE: (): number => { - return ALI_CLOUD_CONSTANTS.PUE_AVG + PUE_TRAILING_TWELVE_MONTH: { + [ALI_REGIONS.EAST_CHINA]: 1.3, + [ALI_REGIONS.SOUTH_CHINA]: 1.3, + [ALI_REGIONS.NORTH_CHINA]: 1.2, + [ALI_REGIONS.UNKNOWN]: 1.3, + }, + getPUE: (region: string): number => { + return ALI_CLOUD_CONSTANTS.PUE_TRAILING_TWELVE_MONTH[region] + ? ALI_CLOUD_CONSTANTS.PUE_TRAILING_TWELVE_MONTH[region] + : ALI_CLOUD_CONSTANTS.PUE_AVG }, AVG_CPU_UTILIZATION_2020: 50, REPLICATION_FACTORS: { @@ -136,5 +145,9 @@ export const ALI_CLOUD_CONSTANTS: CloudConstantsByProvider = { export const ALI_EMISSIONS_FACTORS_METRIC_TON_PER_KWH: CloudConstantsEmissionsFactors = { + ['华东']: 0.000626, + ['华南']: 0.000585, + ['华北']: 0.000665, + ['香港']: 0.000429, ['Unknown']: 0.0003512799615, // Average of above regions } diff --git a/packages/ali/src/lib/AliRegions.ts b/packages/ali/src/lib/AliRegions.ts new file mode 100644 index 000000000..7e7708c90 --- /dev/null +++ b/packages/ali/src/lib/AliRegions.ts @@ -0,0 +1,11 @@ +/* + * © 2023 Thoughtworks, Inc. + */ + +export enum ALI_REGIONS { + SOUTH_CHINA = '华南', + NORTH_CHINA = '华北', + EAST_CHINA = '华东', + SOUTHWEST = '西南', + UNKNOWN = 'Unknown', +} From 0c6391ce2ebd4f822fb9eff5238ed9e6d870bc87 Mon Sep 17 00:00:00 2001 From: dean Date: Thu, 16 Mar 2023 16:07:05 +0800 Subject: [PATCH 095/251] add get memory footprint estimate --- .../ali/src/lib/AliCostAndUsageService.ts | 45 ++++++++++++++++++- 1 file changed, 43 insertions(+), 2 deletions(-) diff --git a/packages/ali/src/lib/AliCostAndUsageService.ts b/packages/ali/src/lib/AliCostAndUsageService.ts index cad88857d..933922ca6 100644 --- a/packages/ali/src/lib/AliCostAndUsageService.ts +++ b/packages/ali/src/lib/AliCostAndUsageService.ts @@ -3,6 +3,8 @@ */ import { + accumulateKilowattHours, + AccumulateKilowattHoursBy, CloudConstants, CloudConstantsEmissionsFactors, COMPUTE_PROCESSOR_TYPES, @@ -11,6 +13,7 @@ import { EmbodiedEmissionsEstimator, FootprintEstimate, MemoryEstimator, + MemoryUsage, NetworkingEstimator, StorageEstimator, UnknownEstimator, @@ -36,6 +39,9 @@ import { INSTANCE_TYPE_COMPUTE_PROCESSOR_MAPPING, } from './AliTypes' import { AZURE_CLOUD_CONSTANTS } from '@cloud-carbon-footprint/azure' +import { INSTANCE_TYPE_COMPUTE_PROCESSOR_MAPPING } from './AliTypes' +import { AZURE_CLOUD_CONSTANTS } from '@cloud-carbon-footprint/azure' +import { ALI_REPLICATION_FACTORS_FOR_SERVICES } from './ReplicationFactors' export default class AliCostAndUsageService { private readonly logger: Logger @@ -79,14 +85,22 @@ export default class AliCostAndUsageService { emissionsFactors, ) + const memoryFootprintEstimate = this.getMemoryFootprintEstimate( + row, + pue, + emissionsFactors, + ) + this.logger.info('row:' + JSON.stringify(row)) serviceEstimates.push({ cloudProvider: 'AliCloud', accountName: row.accountName, serviceName: row.serviceName, accountId: row.accountId, - kilowattHours: computeFootprintEstimate.kilowattHours, - co2e: computeFootprintEstimate.co2e, + kilowattHours: + computeFootprintEstimate.kilowattHours + + memoryFootprintEstimate.kilowattHours, + co2e: computeFootprintEstimate.co2e + memoryFootprintEstimate.co2e, cost: row.cost, region: row.region, usesAverageCPUConstant: false, @@ -216,6 +230,33 @@ export default class AliCostAndUsageService { return computeFootprintEstimate } + private getMemoryFootprintEstimate( + consumptionDetailRow: AliCalculateRow, + powerUsageEffectiveness: number, + emissionsFactors: CloudConstantsEmissionsFactors, + ): FootprintEstimate { + const usage: MemoryUsage = { + timestamp: consumptionDetailRow.timestamp, + gigabyteHours: consumptionDetailRow.usageAmount, + } + + const memoryConstants: CloudConstants = { + powerUsageEffectiveness: powerUsageEffectiveness, + replicationFactor: this.getReplicationFactor(consumptionDetailRow), + } + + const memoryEstimate = this.memoryEstimator.estimate( + [usage], + consumptionDetailRow.region, + emissionsFactors, + memoryConstants, + )[0] + + memoryEstimate.usesAverageCPUConstant = false + + return memoryEstimate + } + private getComputeProcessorsFromAliInstanceType(instanceType: string) { return ( INSTANCE_TYPE_COMPUTE_PROCESSOR_MAPPING[instanceType] || [ From 00691655e0bbfeb89008410e9f3fc4a90d75afe0 Mon Sep 17 00:00:00 2001 From: dean Date: Fri, 17 Mar 2023 15:48:46 +0800 Subject: [PATCH 096/251] fix bug no bill continue not break --- packages/ali/src/lib/AliCostAndUsageService.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ali/src/lib/AliCostAndUsageService.ts b/packages/ali/src/lib/AliCostAndUsageService.ts index 933922ca6..ce4b784dd 100644 --- a/packages/ali/src/lib/AliCostAndUsageService.ts +++ b/packages/ali/src/lib/AliCostAndUsageService.ts @@ -75,7 +75,7 @@ export default class AliCostAndUsageService { aliConfig.authentication.accessKeySecret, ) this.logger.info('response:' + JSON.stringify(response)) - if (response.body.data.totalCount <= 0) break + if (response.body.data.totalCount <= 0) continue response.body.data.items.forEach((cur) => { const row = new AliCalculateRow(cur) const pue = ALI_CLOUD_CONSTANTS.getPUE() From 188387586611b7b6ce2b455eae509be35607eb9f Mon Sep 17 00:00:00 2001 From: dean Date: Mon, 20 Mar 2023 08:53:22 +0800 Subject: [PATCH 097/251] add memory calculate --- packages/ali/src/lib/AliCalculateRow.ts | 19 +++++++++++++++++++ .../ali/src/lib/AliCostAndUsageService.ts | 7 +------ 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/packages/ali/src/lib/AliCalculateRow.ts b/packages/ali/src/lib/AliCalculateRow.ts index 8b8db78ad..cb499375d 100644 --- a/packages/ali/src/lib/AliCalculateRow.ts +++ b/packages/ali/src/lib/AliCalculateRow.ts @@ -23,6 +23,10 @@ export default class AliCalculateRow extends BillingDataRow { this.vCpuHours = this.getVCpuHours(usageDetail) this.gpuHours = this.getGpuHours(usageDetail) this.replicationFactor = this.getReplicationFactor() + this.cost = usageDetail.cashAmount + this.accountName = usageDetail.billAccountName + this.accountId = usageDetail.billAccountID + this.usageAmount = this.getMemoryHours(usageDetail) this.specificationFamily = this.seriesName.split('.').slice(0, 2).join('.') } @@ -37,6 +41,13 @@ export default class AliCalculateRow extends BillingDataRow { return this.getUsage() * this.parseCpuAndGpu(instanceConfig, 'CPU') } + private getMemoryHours( + usageDetail: DescribeInstanceBillResponseBodyDataItems, + ) { + const instanceConfig = usageDetail.instanceConfig + return this.getUsage() * this.parseMemory(instanceConfig) + } + private getGpuHours(usageDetail: DescribeInstanceBillResponseBodyDataItems) { const instanceConfig = usageDetail.instanceConfig return this.getUsage() * this.parseCpuAndGpu(instanceConfig, 'GPU') @@ -50,6 +61,14 @@ export default class AliCalculateRow extends BillingDataRow { return parseInt(keyValue.split('核')[0]) } + private parseMemory(instanceConfig: string): number { + const keyValue = this.getJsonValue('内存', instanceConfig) + if (keyValue == null) { + return 1 + } + return parseInt(keyValue.split('GB')[0]) + } + private getUsage() { return 30 * 24 } diff --git a/packages/ali/src/lib/AliCostAndUsageService.ts b/packages/ali/src/lib/AliCostAndUsageService.ts index ce4b784dd..8fcb9336f 100644 --- a/packages/ali/src/lib/AliCostAndUsageService.ts +++ b/packages/ali/src/lib/AliCostAndUsageService.ts @@ -3,8 +3,6 @@ */ import { - accumulateKilowattHours, - AccumulateKilowattHoursBy, CloudConstants, CloudConstantsEmissionsFactors, COMPUTE_PROCESSOR_TYPES, @@ -39,9 +37,6 @@ import { INSTANCE_TYPE_COMPUTE_PROCESSOR_MAPPING, } from './AliTypes' import { AZURE_CLOUD_CONSTANTS } from '@cloud-carbon-footprint/azure' -import { INSTANCE_TYPE_COMPUTE_PROCESSOR_MAPPING } from './AliTypes' -import { AZURE_CLOUD_CONSTANTS } from '@cloud-carbon-footprint/azure' -import { ALI_REPLICATION_FACTORS_FOR_SERVICES } from './ReplicationFactors' export default class AliCostAndUsageService { private readonly logger: Logger @@ -242,7 +237,7 @@ export default class AliCostAndUsageService { const memoryConstants: CloudConstants = { powerUsageEffectiveness: powerUsageEffectiveness, - replicationFactor: this.getReplicationFactor(consumptionDetailRow), + replicationFactor: consumptionDetailRow.replicationFactor, } const memoryEstimate = this.memoryEstimator.estimate( From ad1cd3ddecc033356c570f642f1ddd8e62e64e10 Mon Sep 17 00:00:00 2001 From: jianglinwang Date: Mon, 20 Mar 2023 14:59:44 +0800 Subject: [PATCH 098/251] add ali embodied emissions logic. --- packages/ali/src/lib/AliCalculateRow.ts | 1 + .../ali/src/lib/AliCostAndUsageService.ts | 48 ++++++++++++++++++- packages/ali/src/lib/AliTypes.ts | 9 ++++ 3 files changed, 56 insertions(+), 2 deletions(-) diff --git a/packages/ali/src/lib/AliCalculateRow.ts b/packages/ali/src/lib/AliCalculateRow.ts index cb499375d..a5e3a506d 100644 --- a/packages/ali/src/lib/AliCalculateRow.ts +++ b/packages/ali/src/lib/AliCalculateRow.ts @@ -20,6 +20,7 @@ export default class AliCalculateRow extends BillingDataRow { // this.usageType = this.parseUsageType() this.seriesName = this.getSeriesName(usageDetail) + this.instanceType = usageDetail.productCode this.vCpuHours = this.getVCpuHours(usageDetail) this.gpuHours = this.getGpuHours(usageDetail) this.replicationFactor = this.getReplicationFactor() diff --git a/packages/ali/src/lib/AliCostAndUsageService.ts b/packages/ali/src/lib/AliCostAndUsageService.ts index 8fcb9336f..f48c569f9 100644 --- a/packages/ali/src/lib/AliCostAndUsageService.ts +++ b/packages/ali/src/lib/AliCostAndUsageService.ts @@ -9,6 +9,7 @@ import { ComputeEstimator, ComputeUsage, EmbodiedEmissionsEstimator, + EmbodiedEmissionsUsage, FootprintEstimate, MemoryEstimator, MemoryUsage, @@ -86,6 +87,11 @@ export default class AliCostAndUsageService { emissionsFactors, ) + const embodiedEmissions = this.getEmbodiedEmissions( + row, + emissionsFactors, + ) + this.logger.info('row:' + JSON.stringify(row)) serviceEstimates.push({ cloudProvider: 'AliCloud', @@ -94,8 +100,12 @@ export default class AliCostAndUsageService { accountId: row.accountId, kilowattHours: computeFootprintEstimate.kilowattHours + - memoryFootprintEstimate.kilowattHours, - co2e: computeFootprintEstimate.co2e + memoryFootprintEstimate.co2e, + memoryFootprintEstimate.kilowattHours + + embodiedEmissions.kilowattHours, + co2e: + computeFootprintEstimate.co2e + + memoryFootprintEstimate.co2e + + embodiedEmissions.co2e, cost: row.cost, region: row.region, usesAverageCPUConstant: false, @@ -271,4 +281,38 @@ export default class AliCostAndUsageService { ] ) } + + private getEmbodiedEmissions( + row: AliCalculateRow, + emissionsFactors: CloudConstantsEmissionsFactors, + ): FootprintEstimate { + const { instancevCpu, scopeThreeEmissions, largestInstancevCpu } = + this.getDataFromSeriesName(row.seriesName) + + if (!instancevCpu || !scopeThreeEmissions || !largestInstancevCpu) + return { + timestamp: new Date(), + kilowattHours: 0, + co2e: 0, + } + + const embodiedEmissionsUsage: EmbodiedEmissionsUsage = { + instancevCpu, + largestInstancevCpu, + usageTimePeriod: row.usageAmount / instancevCpu, + scopeThreeEmissions, + } + + return this.embodiedEmissionsEstimator.estimate( + [embodiedEmissionsUsage], + row.region, + emissionsFactors, + )[0] + } + + private getDataFromSeriesName(seriesName: string) { + this.logger.info('getDataFromSeriesName seriesName:' + seriesName) + // 阿里云各个服务的scope 3 emissions 数据拿不到 + return {} + } } diff --git a/packages/ali/src/lib/AliTypes.ts b/packages/ali/src/lib/AliTypes.ts index 6b96155b0..2ff80deb4 100644 --- a/packages/ali/src/lib/AliTypes.ts +++ b/packages/ali/src/lib/AliTypes.ts @@ -7,6 +7,15 @@ import { COMPUTE_PROCESSOR_TYPES } from '@cloud-carbon-footprint/core' export const INSTANCE_TYPE_COMPUTE_PROCESSOR_MAPPING: { [series: string]: string[] } = { + 'ecs.g8ae': [COMPUTE_PROCESSOR_TYPES.AMD_EPYC_1ST_GEN], + 'ecs.g7se': [COMPUTE_PROCESSOR_TYPES.ICELAKE], + 'ecs.g7a': [COMPUTE_PROCESSOR_TYPES.AMD_EPYC_1ST_GEN], + 'ecs.g7': [COMPUTE_PROCESSOR_TYPES.ICELAKE], + 'ecs.g7t': [COMPUTE_PROCESSOR_TYPES.ICELAKE], + 'ecs.g7nex': [COMPUTE_PROCESSOR_TYPES.ICELAKE], + 'ecs.g6t': [COMPUTE_PROCESSOR_TYPES.CASCADE_LAKE], + 'ecs.g6e': [COMPUTE_PROCESSOR_TYPES.CASCADE_LAKE], + 'ecs.g6': [COMPUTE_PROCESSOR_TYPES.CASCADE_LAKE], 'ecs.s6': [COMPUTE_PROCESSOR_TYPES.CASCADE_LAKE], } From 5180746643bf795d8f3fbd2917414b32ceb90d06 Mon Sep 17 00:00:00 2001 From: jianglinwang Date: Mon, 20 Mar 2023 16:01:58 +0800 Subject: [PATCH 099/251] add aliyun regions and modify get usage method. --- .../domain/AliFootprintEstimationConstants.ts | 31 ++++++++--- packages/ali/src/lib/AliCalculateRow.ts | 54 +++++++++++++++++-- .../ali/src/lib/AliCostAndUsageService.ts | 12 +++-- packages/ali/src/lib/AliRegions.ts | 16 ++++-- packages/ali/src/lib/AliTypes.ts | 2 +- 5 files changed, 93 insertions(+), 22 deletions(-) diff --git a/packages/ali/src/domain/AliFootprintEstimationConstants.ts b/packages/ali/src/domain/AliFootprintEstimationConstants.ts index 5de90387d..f85404338 100644 --- a/packages/ali/src/domain/AliFootprintEstimationConstants.ts +++ b/packages/ali/src/domain/AliFootprintEstimationConstants.ts @@ -121,9 +121,17 @@ export const ALI_CLOUD_CONSTANTS: CloudConstantsByProvider = { MEMORY_COEFFICIENT: 0.000392, // kWh / Gb PUE_AVG: 1.3, PUE_TRAILING_TWELVE_MONTH: { - [ALI_REGIONS.EAST_CHINA]: 1.3, - [ALI_REGIONS.SOUTH_CHINA]: 1.3, - [ALI_REGIONS.NORTH_CHINA]: 1.2, + [ALI_REGIONS.CN_HANGZHOU]: 1.3, + [ALI_REGIONS.CN_SHANGHAI]: 1.3, + [ALI_REGIONS.CN_NANJING]: 1.3, + [ALI_REGIONS.CN_SHENZHEN]: 1.3, + [ALI_REGIONS.CN_HEYUAN]: 1.3, + [ALI_REGIONS.CN_GUANGZHOU]: 1.3, + [ALI_REGIONS.CN_QINGDAP]: 1.2, + [ALI_REGIONS.CN_BEIJING]: 1.2, + [ALI_REGIONS.CN_ZHANGJIAKOU]: 1.2, + [ALI_REGIONS.CN_HUHEHAOTE]: 1.2, + [ALI_REGIONS.CN_WULANCHABU]: 1.2, [ALI_REGIONS.UNKNOWN]: 1.3, }, getPUE: (region: string): number => { @@ -145,9 +153,16 @@ export const ALI_CLOUD_CONSTANTS: CloudConstantsByProvider = { export const ALI_EMISSIONS_FACTORS_METRIC_TON_PER_KWH: CloudConstantsEmissionsFactors = { - ['华东']: 0.000626, - ['华南']: 0.000585, - ['华北']: 0.000665, - ['香港']: 0.000429, - ['Unknown']: 0.0003512799615, // Average of above regions + [ALI_REGIONS.CN_HANGZHOU]: 0.000626, + [ALI_REGIONS.CN_SHANGHAI]: 0.000626, + [ALI_REGIONS.CN_NANJING]: 0.000626, + [ALI_REGIONS.CN_SHENZHEN]: 0.000585, + [ALI_REGIONS.CN_HEYUAN]: 0.000585, + [ALI_REGIONS.CN_GUANGZHOU]: 0.000585, + [ALI_REGIONS.CN_QINGDAP]: 0.000665, + [ALI_REGIONS.CN_BEIJING]: 0.000665, + [ALI_REGIONS.CN_ZHANGJIAKOU]: 0.000665, + [ALI_REGIONS.CN_HUHEHAOTE]: 0.000665, + [ALI_REGIONS.CN_WULANCHABU]: 0.000665, + [ALI_REGIONS.UNKNOWN]: 0.0006512799615, // Average of above regions } diff --git a/packages/ali/src/lib/AliCalculateRow.ts b/packages/ali/src/lib/AliCalculateRow.ts index a5e3a506d..081107726 100644 --- a/packages/ali/src/lib/AliCalculateRow.ts +++ b/packages/ali/src/lib/AliCalculateRow.ts @@ -39,19 +39,28 @@ export default class AliCalculateRow extends BillingDataRow { private getVCpuHours(usageDetail: DescribeInstanceBillResponseBodyDataItems) { const instanceConfig = usageDetail.instanceConfig - return this.getUsage() * this.parseCpuAndGpu(instanceConfig, 'CPU') + return ( + this.getUsage(usageDetail.servicePeriod, usageDetail.servicePeriodUnit) * + this.parseCpuAndGpu(instanceConfig, 'CPU') + ) } private getMemoryHours( usageDetail: DescribeInstanceBillResponseBodyDataItems, ) { const instanceConfig = usageDetail.instanceConfig - return this.getUsage() * this.parseMemory(instanceConfig) + return ( + this.getUsage(usageDetail.servicePeriod, usageDetail.servicePeriodUnit) * + this.parseMemory(instanceConfig) + ) } private getGpuHours(usageDetail: DescribeInstanceBillResponseBodyDataItems) { const instanceConfig = usageDetail.instanceConfig - return this.getUsage() * this.parseCpuAndGpu(instanceConfig, 'GPU') + return ( + this.getUsage(usageDetail.servicePeriod, usageDetail.servicePeriodUnit) * + this.parseCpuAndGpu(instanceConfig, 'GPU') + ) } private parseCpuAndGpu(instanceConfig: string, key: string): number { @@ -70,8 +79,43 @@ export default class AliCalculateRow extends BillingDataRow { return parseInt(keyValue.split('GB')[0]) } - private getUsage() { - return 30 * 24 + private getUsage(servicePeriod: string, servicePeriodUnit: string) { + let ratio = 1 + switch (servicePeriodUnit) { + case '秒': + { + ratio = 1 / 3600 + } + break + case '分': + { + ratio = 1 / 60 + } + break + case '时': + { + ratio = 1 + } + break + case '天': + { + ratio = 24 + } + break + case '月': + { + ratio = 24 * 30 + } + break + case '年': + { + ratio = 24 * 30 * 12 + } + break + default: + break + } + return parseInt(servicePeriod) * ratio } public getJsonValue(key: string, jsonStr: string): string { diff --git a/packages/ali/src/lib/AliCostAndUsageService.ts b/packages/ali/src/lib/AliCostAndUsageService.ts index f48c569f9..2ac2d0e40 100644 --- a/packages/ali/src/lib/AliCostAndUsageService.ts +++ b/packages/ali/src/lib/AliCostAndUsageService.ts @@ -35,7 +35,7 @@ import { } from '../domain' import { GPU_VIRTUAL_MACHINE_TYPE_PROCESSOR_MAPPING, - INSTANCE_TYPE_COMPUTE_PROCESSOR_MAPPING, + SPECIFICATION_FAMILY_COMPUTE_PROCESSOR_MAPPING, } from './AliTypes' import { AZURE_CLOUD_CONSTANTS } from '@cloud-carbon-footprint/azure' @@ -172,7 +172,7 @@ export default class AliCostAndUsageService { vCpuHours: row.vCpuHours, usesAverageCPUConstant: true, } - const processors = this.getComputeProcessorsFromAliInstanceType( + const processors = this.getComputeProcessorsFromAliSpecificationFamily( row.specificationFamily, ) const cpuComputeConstants: CloudConstants = { @@ -262,9 +262,11 @@ export default class AliCostAndUsageService { return memoryEstimate } - private getComputeProcessorsFromAliInstanceType(instanceType: string) { + private getComputeProcessorsFromAliSpecificationFamily( + specificationFamily: string, + ) { return ( - INSTANCE_TYPE_COMPUTE_PROCESSOR_MAPPING[instanceType] || [ + SPECIFICATION_FAMILY_COMPUTE_PROCESSOR_MAPPING[specificationFamily] || [ COMPUTE_PROCESSOR_TYPES.UNKNOWN, ] ) @@ -286,6 +288,8 @@ export default class AliCostAndUsageService { row: AliCalculateRow, emissionsFactors: CloudConstantsEmissionsFactors, ): FootprintEstimate { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore const { instancevCpu, scopeThreeEmissions, largestInstancevCpu } = this.getDataFromSeriesName(row.seriesName) diff --git a/packages/ali/src/lib/AliRegions.ts b/packages/ali/src/lib/AliRegions.ts index 7e7708c90..eb89dd0ff 100644 --- a/packages/ali/src/lib/AliRegions.ts +++ b/packages/ali/src/lib/AliRegions.ts @@ -3,9 +3,17 @@ */ export enum ALI_REGIONS { - SOUTH_CHINA = '华南', - NORTH_CHINA = '华北', - EAST_CHINA = '华东', - SOUTHWEST = '西南', + CN_QINGDAP = '华北1(青岛)', + CN_BEIJING = '华北2(北京)', + CN_ZHANGJIAKOU = '华北3(张家口)', + CN_HUHEHAOTE = '华北5(呼和浩特)', + CN_WULANCHABU = '华北6(乌兰察布)', + CN_HANGZHOU = '华东1(杭州)', + CN_SHANGHAI = '华东2(上海)', + CN_SHENZHEN = '华南1(深圳)', + CN_HEYUAN = '华南2(河源)', + CN_GUANGZHOU = '华南3(广州)', + CN_CHENGDU = '西南1(成都)', + CN_NANJING = '华东5(南京)', UNKNOWN = 'Unknown', } diff --git a/packages/ali/src/lib/AliTypes.ts b/packages/ali/src/lib/AliTypes.ts index 2ff80deb4..f54d75fe9 100644 --- a/packages/ali/src/lib/AliTypes.ts +++ b/packages/ali/src/lib/AliTypes.ts @@ -4,7 +4,7 @@ import { COMPUTE_PROCESSOR_TYPES } from '@cloud-carbon-footprint/core' -export const INSTANCE_TYPE_COMPUTE_PROCESSOR_MAPPING: { +export const SPECIFICATION_FAMILY_COMPUTE_PROCESSOR_MAPPING: { [series: string]: string[] } = { 'ecs.g8ae': [COMPUTE_PROCESSOR_TYPES.AMD_EPYC_1ST_GEN], From 59d573363e0e035d3510298b82bad55f3672e3b7 Mon Sep 17 00:00:00 2001 From: jianglinwang Date: Tue, 21 Mar 2023 15:06:25 +0800 Subject: [PATCH 100/251] add all ecs instance types. --- packages/ali/src/lib/AliCalculateRow.ts | 12 +- .../ali/src/lib/AliCostAndUsageService.ts | 14 +- packages/ali/src/lib/AliTypes.ts | 133 ++++++++++++++++++ 3 files changed, 155 insertions(+), 4 deletions(-) diff --git a/packages/ali/src/lib/AliCalculateRow.ts b/packages/ali/src/lib/AliCalculateRow.ts index 081107726..4edd727cd 100644 --- a/packages/ali/src/lib/AliCalculateRow.ts +++ b/packages/ali/src/lib/AliCalculateRow.ts @@ -8,6 +8,7 @@ import { ALI_REPLICATION_FACTORS_FOR_SERVICES } from './ReplicationFactors' export default class AliCalculateRow extends BillingDataRow { readonly specificationFamily: string + constructor(usageDetail: DescribeInstanceBillResponseBodyDataItems) { // const consumptionDetails = getConsumptionDetails(usageDetail) super({ @@ -28,7 +29,16 @@ export default class AliCalculateRow extends BillingDataRow { this.accountName = usageDetail.billAccountName this.accountId = usageDetail.billAccountID this.usageAmount = this.getMemoryHours(usageDetail) - this.specificationFamily = this.seriesName.split('.').slice(0, 2).join('.') + this.specificationFamily = this.getSpecificationFamily() + } + + private getSpecificationFamily() { + const seriesNameArr = this.seriesName.split('.') + if (seriesNameArr.length <= 1) { + return seriesNameArr[0] + } + const second = seriesNameArr[1].split('-')[0] + return seriesNameArr[0] + '.' + second } private getSeriesName( diff --git a/packages/ali/src/lib/AliCostAndUsageService.ts b/packages/ali/src/lib/AliCostAndUsageService.ts index 2ac2d0e40..cdcc6380a 100644 --- a/packages/ali/src/lib/AliCostAndUsageService.ts +++ b/packages/ali/src/lib/AliCostAndUsageService.ts @@ -91,7 +91,13 @@ export default class AliCostAndUsageService { row, emissionsFactors, ) - + this.logger.info( + 'computeFootprintEstimate:' + + JSON.stringify(computeFootprintEstimate), + ) + this.logger.info( + 'memoryFootprintEstimate:' + JSON.stringify(memoryFootprintEstimate), + ) this.logger.info('row:' + JSON.stringify(row)) serviceEstimates.push({ cloudProvider: 'AliCloud', @@ -276,9 +282,11 @@ export default class AliCostAndUsageService { return gpuHours != 0 } - private getGPUComputeProcessorsFromAliInstanceType(instanceType: string) { + private getGPUComputeProcessorsFromAliInstanceType( + specificationFamily: string, + ) { return ( - GPU_VIRTUAL_MACHINE_TYPE_PROCESSOR_MAPPING[instanceType] || [ + GPU_VIRTUAL_MACHINE_TYPE_PROCESSOR_MAPPING[specificationFamily] || [ COMPUTE_PROCESSOR_TYPES.UNKNOWN, ] ) diff --git a/packages/ali/src/lib/AliTypes.ts b/packages/ali/src/lib/AliTypes.ts index f54d75fe9..325f6ea50 100644 --- a/packages/ali/src/lib/AliTypes.ts +++ b/packages/ali/src/lib/AliTypes.ts @@ -16,6 +16,132 @@ export const SPECIFICATION_FAMILY_COMPUTE_PROCESSOR_MAPPING: { 'ecs.g6t': [COMPUTE_PROCESSOR_TYPES.CASCADE_LAKE], 'ecs.g6e': [COMPUTE_PROCESSOR_TYPES.CASCADE_LAKE], 'ecs.g6': [COMPUTE_PROCESSOR_TYPES.CASCADE_LAKE], + 'ecs.sn2ne': [COMPUTE_PROCESSOR_TYPES.SKYLAKE], + 'ecs.g5': [ + COMPUTE_PROCESSOR_TYPES.CASCADE_LAKE, + COMPUTE_PROCESSOR_TYPES.SKYLAKE, + ], + 'ecs.g5ne': [ + COMPUTE_PROCESSOR_TYPES.CASCADE_LAKE, + COMPUTE_PROCESSOR_TYPES.SKYLAKE, + ], + 'ecs.c8ae': [COMPUTE_PROCESSOR_TYPES.AMD_EPYC_1ST_GEN], + 'ecs.c7re': [COMPUTE_PROCESSOR_TYPES.ICELAKE], + 'ecs.c7se': [COMPUTE_PROCESSOR_TYPES.ICELAKE], + 'ecs.c7nex': [COMPUTE_PROCESSOR_TYPES.ICELAKE], + 'ecs.c7': [COMPUTE_PROCESSOR_TYPES.ICELAKE], + 'ecs.c7t': [COMPUTE_PROCESSOR_TYPES.ICELAKE], + 'ecs.c6t': [COMPUTE_PROCESSOR_TYPES.CASCADE_LAKE], + 'ecs.c6e': [COMPUTE_PROCESSOR_TYPES.CASCADE_LAKE], + 'ecs.c6': [COMPUTE_PROCESSOR_TYPES.CASCADE_LAKE], + 'ecs.c5': [ + COMPUTE_PROCESSOR_TYPES.CASCADE_LAKE, + COMPUTE_PROCESSOR_TYPES.SKYLAKE, + ], + 'ecs.ic5': [ + COMPUTE_PROCESSOR_TYPES.CASCADE_LAKE, + COMPUTE_PROCESSOR_TYPES.SKYLAKE, + ], + 'ecs.sn1ne': [ + COMPUTE_PROCESSOR_TYPES.SKYLAKE, + COMPUTE_PROCESSOR_TYPES.BROADWELL, + ], + 'ecs.r8ae': [COMPUTE_PROCESSOR_TYPES.AMD_EPYC_1ST_GEN], + 'ecs.re7p': [COMPUTE_PROCESSOR_TYPES.ICELAKE], + 'ecs.r7p': [COMPUTE_PROCESSOR_TYPES.ICELAKE], + 'ecs.r7se': [COMPUTE_PROCESSOR_TYPES.ICELAKE], + 'ecs.r7': [COMPUTE_PROCESSOR_TYPES.ICELAKE], + 'ecs.r7t': [COMPUTE_PROCESSOR_TYPES.ICELAKE], + 'ecs.re6p': [COMPUTE_PROCESSOR_TYPES.CASCADE_LAKE], + 'ecs.r6e': [COMPUTE_PROCESSOR_TYPES.CASCADE_LAKE], + 'ecs.r6': [COMPUTE_PROCESSOR_TYPES.CASCADE_LAKE], + 'ecs.re6': [COMPUTE_PROCESSOR_TYPES.CASCADE_LAKE], + 'ecs.r5': [ + COMPUTE_PROCESSOR_TYPES.CASCADE_LAKE, + COMPUTE_PROCESSOR_TYPES.SKYLAKE, + ], + 'ecs.re4': [COMPUTE_PROCESSOR_TYPES.BROADWELL], + 'ecs.re4e': [COMPUTE_PROCESSOR_TYPES.BROADWELL], + 'ecs.se1ne': [ + COMPUTE_PROCESSOR_TYPES.BROADWELL, + COMPUTE_PROCESSOR_TYPES.SKYLAKE, + ], + 'ecs.se1': [COMPUTE_PROCESSOR_TYPES.BROADWELL], + 'ecs.d3s': [COMPUTE_PROCESSOR_TYPES.ICELAKE], + 'ecs.d3c': [COMPUTE_PROCESSOR_TYPES.ICELAKE], + 'ecs.d2c': [COMPUTE_PROCESSOR_TYPES.CASCADE_LAKE], + 'ecs.d2s': [COMPUTE_PROCESSOR_TYPES.SKYLAKE], + 'ecs.d1ne': [COMPUTE_PROCESSOR_TYPES.BROADWELL], + 'ecs.d1': [COMPUTE_PROCESSOR_TYPES.BROADWELL], + 'ecs.i4': [COMPUTE_PROCESSOR_TYPES.ICELAKE], + 'ecs.i4g': [COMPUTE_PROCESSOR_TYPES.ICELAKE], + 'ecs.i4r': [COMPUTE_PROCESSOR_TYPES.ICELAKE], + 'ecs.i4p': [COMPUTE_PROCESSOR_TYPES.ICELAKE], + 'ecs.i3g': [COMPUTE_PROCESSOR_TYPES.CASCADE_LAKE], + 'ecs.i3': [COMPUTE_PROCESSOR_TYPES.CASCADE_LAKE], + 'ecs.i2': [COMPUTE_PROCESSOR_TYPES.SKYLAKE], + 'ecs.i2g': [COMPUTE_PROCESSOR_TYPES.SKYLAKE], + 'ecs.i2ne': [COMPUTE_PROCESSOR_TYPES.SKYLAKE], + 'ecs.i2gne': [COMPUTE_PROCESSOR_TYPES.SKYLAKE], + 'ecs.i1': [COMPUTE_PROCESSOR_TYPES.BROADWELL], + 'ecs.hfc6': [COMPUTE_PROCESSOR_TYPES.CASCADE_LAKE], + 'ecs.hfg6': [COMPUTE_PROCESSOR_TYPES.CASCADE_LAKE], + 'ecs.hfr6': [COMPUTE_PROCESSOR_TYPES.CASCADE_LAKE], + 'ecs.hfc5': [COMPUTE_PROCESSOR_TYPES.SKYLAKE], + 'ecs.hfg5': [COMPUTE_PROCESSOR_TYPES.SKYLAKE], + 'ecs.sgn7i-vws': [COMPUTE_PROCESSOR_TYPES.ICELAKE], + 'ecs.vgn7i-vws': [COMPUTE_PROCESSOR_TYPES.ICELAKE], + 'ecs.gn7s': [COMPUTE_PROCESSOR_TYPES.ICELAKE], + 'ecs.gn7e': [COMPUTE_PROCESSOR_TYPES.ICELAKE], + 'ecs.gn7i': [COMPUTE_PROCESSOR_TYPES.ICELAKE], + 'ecs.gn7': [COMPUTE_PROCESSOR_TYPES.CASCADE_LAKE], + 'ecs.vgn6i': [COMPUTE_PROCESSOR_TYPES.SKYLAKE], + 'ecs.gn6i': [COMPUTE_PROCESSOR_TYPES.SKYLAKE], + 'ecs.gn6e': [COMPUTE_PROCESSOR_TYPES.SKYLAKE], + 'ecs.gn6v': [COMPUTE_PROCESSOR_TYPES.SKYLAKE], + 'ecs.vgn5i': [COMPUTE_PROCESSOR_TYPES.BROADWELL], + 'ecs.gn5': [COMPUTE_PROCESSOR_TYPES.BROADWELL], + 'ecs.gn5i': [COMPUTE_PROCESSOR_TYPES.BROADWELL], + 'ecs.f5': [COMPUTE_PROCESSOR_TYPES.ICELAKE], + 'ecs.f3': [COMPUTE_PROCESSOR_TYPES.SKYLAKE], + 'ecs.ebman1': [COMPUTE_PROCESSOR_TYPES.SKYLAKE], + 'ecs.ebmg7se': [COMPUTE_PROCESSOR_TYPES.ICELAKE], + 'ecs.ebmg7': [COMPUTE_PROCESSOR_TYPES.ICELAKE], + 'ecs.ebmg6e': [COMPUTE_PROCESSOR_TYPES.CASCADE_LAKE], + 'ecs.ebmg6': [COMPUTE_PROCESSOR_TYPES.CASCADE_LAKE], + 'ecs.ebmc7': [COMPUTE_PROCESSOR_TYPES.ICELAKE], + 'ecs.ebmc6me': [COMPUTE_PROCESSOR_TYPES.CASCADE_LAKE], + 'ecs.ebmc6e': [COMPUTE_PROCESSOR_TYPES.CASCADE_LAKE], + 'ecs.ebmc6': [COMPUTE_PROCESSOR_TYPES.CASCADE_LAKE], + 'ecs.ebmr7': [COMPUTE_PROCESSOR_TYPES.ICELAKE], + 'ecs.ebmr6e': [COMPUTE_PROCESSOR_TYPES.CASCADE_LAKE], + 'ecs.ebmr6': [COMPUTE_PROCESSOR_TYPES.CASCADE_LAKE], + 'ecs.ebmre6p': [COMPUTE_PROCESSOR_TYPES.CASCADE_LAKE], + 'ecs.ebmre6': [COMPUTE_PROCESSOR_TYPES.CASCADE_LAKE], + 'ecs.ebmhfg6': [COMPUTE_PROCESSOR_TYPES.CASCADE_LAKE], + 'ecs.ebmhfc6': [COMPUTE_PROCESSOR_TYPES.CASCADE_LAKE], + 'ecs.ebmhfr6': [COMPUTE_PROCESSOR_TYPES.CASCADE_LAKE], + 'ecs.ebmi2g': [COMPUTE_PROCESSOR_TYPES.SKYLAKE], + 'ecs.ebmgn7ex': [COMPUTE_PROCESSOR_TYPES.ICELAKE], + 'ecs.ebmgn7i': [COMPUTE_PROCESSOR_TYPES.ICELAKE], + 'ecs.ebmgn7': [COMPUTE_PROCESSOR_TYPES.CASCADE_LAKE], + 'ecs.ebmgn6e': [COMPUTE_PROCESSOR_TYPES.SKYLAKE], + 'ecs.ebmgn6v': [COMPUTE_PROCESSOR_TYPES.SKYLAKE], + 'ecs.ebmgn6i': [COMPUTE_PROCESSOR_TYPES.SKYLAKE], + 'ecs.ebmg5s': [COMPUTE_PROCESSOR_TYPES.SKYLAKE], + 'ecs.ebmg5': [COMPUTE_PROCESSOR_TYPES.SKYLAKE], + 'ecs.ebmc4': [COMPUTE_PROCESSOR_TYPES.BROADWELL], + 'ecs.ebmr5s': [COMPUTE_PROCESSOR_TYPES.SKYLAKE], + 'ecs.sccg7': [COMPUTE_PROCESSOR_TYPES.ICELAKE], + 'ecs.sccc7': [COMPUTE_PROCESSOR_TYPES.ICELAKE], + 'ecs.scchfc6': [COMPUTE_PROCESSOR_TYPES.CASCADE_LAKE], + 'ecs.scchfg6': [COMPUTE_PROCESSOR_TYPES.CASCADE_LAKE], + 'ecs.scchfr6': [COMPUTE_PROCESSOR_TYPES.CASCADE_LAKE], + 'ecs.scch5': [COMPUTE_PROCESSOR_TYPES.SKYLAKE], + 'ecs.sccg5': [COMPUTE_PROCESSOR_TYPES.SKYLAKE], + 'ecs.sccgn6e': [COMPUTE_PROCESSOR_TYPES.SKYLAKE], + 'ecs.sccgn6': [COMPUTE_PROCESSOR_TYPES.SKYLAKE], + 'ecs.t6': [COMPUTE_PROCESSOR_TYPES.CASCADE_LAKE], 'ecs.s6': [COMPUTE_PROCESSOR_TYPES.CASCADE_LAKE], } @@ -24,4 +150,11 @@ export const GPU_VIRTUAL_MACHINE_TYPE_PROCESSOR_MAPPING: { } = { 'ecs.sgn7i': [COMPUTE_PROCESSOR_TYPES.NVIDIA_A10G], 'ecs.gn7r': [COMPUTE_PROCESSOR_TYPES.NVIDIA_A10G], + 'ecs.gn7i': [COMPUTE_PROCESSOR_TYPES.NVIDIA_A10G], + 'ecs.vgn6i': [COMPUTE_PROCESSOR_TYPES.NVIDIA_T4], + 'ecs.gn6i': [COMPUTE_PROCESSOR_TYPES.NVIDIA_TESLA_V100], + 'ecs.gn6v': [COMPUTE_PROCESSOR_TYPES.NVIDIA_TESLA_V100], + 'ecs.vgn5i': [COMPUTE_PROCESSOR_TYPES.NVIDIA_TESLA_P4], + 'ecs.gn5': [COMPUTE_PROCESSOR_TYPES.NVIDIA_TESLA_P100], + 'ecs.gn5i': [COMPUTE_PROCESSOR_TYPES.NVIDIA_TESLA_P4], } From 15249c04f402a0650bcdc6fe8801c4a9d382d50c Mon Sep 17 00:00:00 2001 From: dean Date: Tue, 21 Mar 2023 16:03:18 +0800 Subject: [PATCH 101/251] change memory default zero --- packages/ali/src/lib/AliCalculateRow.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ali/src/lib/AliCalculateRow.ts b/packages/ali/src/lib/AliCalculateRow.ts index 4edd727cd..7d0353bd2 100644 --- a/packages/ali/src/lib/AliCalculateRow.ts +++ b/packages/ali/src/lib/AliCalculateRow.ts @@ -84,7 +84,7 @@ export default class AliCalculateRow extends BillingDataRow { private parseMemory(instanceConfig: string): number { const keyValue = this.getJsonValue('内存', instanceConfig) if (keyValue == null) { - return 1 + return 0 } return parseInt(keyValue.split('GB')[0]) } From 30d582e267c97837c64e1285d9402fe30130fcc4 Mon Sep 17 00:00:00 2001 From: dean Date: Tue, 21 Mar 2023 16:52:54 +0800 Subject: [PATCH 102/251] storage network and known estimate --- .../ali/src/lib/AliCostAndUsageService.ts | 160 +++++++++++++++++- packages/ali/src/lib/AliTypes.ts | 16 ++ 2 files changed, 172 insertions(+), 4 deletions(-) diff --git a/packages/ali/src/lib/AliCostAndUsageService.ts b/packages/ali/src/lib/AliCostAndUsageService.ts index cdcc6380a..0a2f6b756 100644 --- a/packages/ali/src/lib/AliCostAndUsageService.ts +++ b/packages/ali/src/lib/AliCostAndUsageService.ts @@ -3,6 +3,8 @@ */ import { + accumulateKilowattHours, + AccumulateKilowattHoursBy, CloudConstants, CloudConstantsEmissionsFactors, COMPUTE_PROCESSOR_TYPES, @@ -14,11 +16,15 @@ import { MemoryEstimator, MemoryUsage, NetworkingEstimator, + NetworkingUsage, StorageEstimator, + StorageUsage, UnknownEstimator, + UnknownUsage, } from '@cloud-carbon-footprint/core' import { configLoader, + containsAny, EstimationResult, GroupBy, Logger, @@ -36,8 +42,13 @@ import { import { GPU_VIRTUAL_MACHINE_TYPE_PROCESSOR_MAPPING, SPECIFICATION_FAMILY_COMPUTE_PROCESSOR_MAPPING, + NETWORKING_USAGE_TYPES, + UNKNOWN_SERVICES, + UNKNOWN_USAGE_TYPES, + SSD_MANAGED_DISKS_STORAGE_GB, + STORAGE_USAGE_TYPES, + HDD_MANAGED_DISKS_STORAGE_GB, } from './AliTypes' -import { AZURE_CLOUD_CONSTANTS } from '@cloud-carbon-footprint/azure' export default class AliCostAndUsageService { private readonly logger: Logger @@ -91,6 +102,21 @@ export default class AliCostAndUsageService { row, emissionsFactors, ) + + const storageFootprintEstimate = this.getStorageFootprintEstimate( + row, + pue, + emissionsFactors, + ) + + const networkingFootprintEstimate = this.getNetworkingFootprintEstimate( + row, + pue, + emissionsFactors, + ) + + const unknownFootprintEstimate = this.getEstimateForUnknownUsage(row) + this.logger.info( 'computeFootprintEstimate:' + JSON.stringify(computeFootprintEstimate), @@ -107,10 +133,16 @@ export default class AliCostAndUsageService { kilowattHours: computeFootprintEstimate.kilowattHours + memoryFootprintEstimate.kilowattHours + + storageFootprintEstimate.kilowattHours + + networkingFootprintEstimate.kilowattHours + + unknownFootprintEstimate.kilowattHours + embodiedEmissions.kilowattHours, co2e: computeFootprintEstimate.co2e + memoryFootprintEstimate.co2e + + storageFootprintEstimate.co2e + + networkingFootprintEstimate.co2e + + unknownFootprintEstimate.co2e + embodiedEmissions.co2e, cost: row.cost, region: row.region, @@ -216,14 +248,14 @@ export default class AliCostAndUsageService { const gpuComputeUsage: ComputeUsage = { timestamp: row.timestamp, - cpuUtilizationAverage: AZURE_CLOUD_CONSTANTS.AVG_CPU_UTILIZATION_2020, + cpuUtilizationAverage: ALI_CLOUD_CONSTANTS.AVG_CPU_UTILIZATION_2020, vCpuHours: row.gpuHours, usesAverageCPUConstant: true, } const gpuComputeConstants: CloudConstants = { - minWatts: AZURE_CLOUD_CONSTANTS.getMinWatts(gpuComputeProcessors), - maxWatts: AZURE_CLOUD_CONSTANTS.getMaxWatts(gpuComputeProcessors), + minWatts: ALI_CLOUD_CONSTANTS.getMinWatts(gpuComputeProcessors), + maxWatts: ALI_CLOUD_CONSTANTS.getMaxWatts(gpuComputeProcessors), powerUsageEffectiveness: powerUsageEffectiveness, replicationFactor: row.replicationFactor, } @@ -327,4 +359,124 @@ export default class AliCostAndUsageService { // 阿里云各个服务的scope 3 emissions 数据拿不到 return {} } + + private getStorageFootprintEstimate( + row: AliCalculateRow, + powerUsageEffectiveness: number, + emissionsFactors: CloudConstantsEmissionsFactors, + ) { + const storageUsage: StorageUsage = { + timestamp: row.timestamp, + terabyteHours: row.usageAmount, + } + + const storageConstants: CloudConstants = { + powerUsageEffectiveness: powerUsageEffectiveness, + replicationFactor: row.replicationFactor, + } + + let estimate: FootprintEstimate + if (this.isSSDStorage(row)) + estimate = this.ssdStorageEstimator.estimate( + [storageUsage], + row.region, + emissionsFactors, + storageConstants, + )[0] + else if (this.isHDDStorage(row)) + estimate = this.hddStorageEstimator.estimate( + [storageUsage], + row.region, + emissionsFactors, + storageConstants, + )[0] + if (estimate) { + estimate.usesAverageCPUConstant = false + accumulateKilowattHours( + ALI_CLOUD_CONSTANTS.KILOWATT_HOURS_BY_SERVICE_AND_USAGE_UNIT, + row, + estimate.kilowattHours, + AccumulateKilowattHoursBy.USAGE_AMOUNT, + ) + } + return estimate + } + + private getNetworkingFootprintEstimate( + consumptionDetailRow: AliCalculateRow, + powerUsageEffectiveness: number, + emissionsFactors: CloudConstantsEmissionsFactors, + ): FootprintEstimate { + let networkingEstimate: FootprintEstimate + if (containsAny(NETWORKING_USAGE_TYPES, consumptionDetailRow.usageType)) { + const networkingUsage: NetworkingUsage = { + timestamp: consumptionDetailRow.timestamp, + gigabytes: consumptionDetailRow.usageAmount, + } + + const networkingConstants: CloudConstants = { + powerUsageEffectiveness: powerUsageEffectiveness, + } + + networkingEstimate = this.networkingEstimator.estimate( + [networkingUsage], + consumptionDetailRow.region, + emissionsFactors, + networkingConstants, + )[0] + } + if (networkingEstimate) { + networkingEstimate.usesAverageCPUConstant = false + accumulateKilowattHours( + ALI_CLOUD_CONSTANTS.KILOWATT_HOURS_BY_SERVICE_AND_USAGE_UNIT, + consumptionDetailRow, + networkingEstimate.kilowattHours, + AccumulateKilowattHoursBy.USAGE_AMOUNT, + ) + } + return networkingEstimate + } + + private getEstimateForUnknownUsage( + rowData: AliCalculateRow, + ): FootprintEstimate { + if ( + containsAny(UNKNOWN_SERVICES, rowData.serviceName) || + containsAny(UNKNOWN_USAGE_TYPES, rowData.usageType) + ) { + const unknownUsage: UnknownUsage = { + timestamp: rowData.timestamp, + usageAmount: rowData.usageAmount, + usageUnit: rowData.usageUnit, + } + + const unknownConstants: CloudConstants = { + kilowattHoursByServiceAndUsageUnit: + ALI_CLOUD_CONSTANTS.KILOWATT_HOURS_BY_SERVICE_AND_USAGE_UNIT, + } + return this.unknownEstimator.estimate( + [unknownUsage], + rowData.region, + ALI_EMISSIONS_FACTORS_METRIC_TON_PER_KWH, + unknownConstants, + )[0] + } + return { + timestamp: rowData.timestamp, + kilowattHours: 0, + co2e: 0, + usesAverageCPUConstant: false, + } + } + + private isSSDStorage(row: AliCalculateRow): boolean { + return ( + containsAny(Object.keys(SSD_MANAGED_DISKS_STORAGE_GB), row.usageType) || + containsAny(STORAGE_USAGE_TYPES, row.usageType) + ) + } + + private isHDDStorage(row: AliCalculateRow): boolean { + return containsAny(Object.keys(HDD_MANAGED_DISKS_STORAGE_GB), row.usageType) + } } diff --git a/packages/ali/src/lib/AliTypes.ts b/packages/ali/src/lib/AliTypes.ts index 325f6ea50..1b837000e 100644 --- a/packages/ali/src/lib/AliTypes.ts +++ b/packages/ali/src/lib/AliTypes.ts @@ -158,3 +158,19 @@ export const GPU_VIRTUAL_MACHINE_TYPE_PROCESSOR_MAPPING: { 'ecs.gn5': [COMPUTE_PROCESSOR_TYPES.NVIDIA_TESLA_P100], 'ecs.gn5i': [COMPUTE_PROCESSOR_TYPES.NVIDIA_TESLA_P4], } + +export const SSD_MANAGED_DISKS_STORAGE_GB: { + [diskType: string]: number +} = {} + +export const HDD_MANAGED_DISKS_STORAGE_GB: { + [diskType: string]: number +} = {} + +export const STORAGE_USAGE_TYPES: string[] = [] + +export const NETWORKING_USAGE_TYPES: string[] = [] + +export const UNKNOWN_SERVICES: string[] = [] + +export const UNKNOWN_USAGE_TYPES: string[] = [] From a5ea5416f54bd25ae292358ffcfdfbfecbdc0cb0 Mon Sep 17 00:00:00 2001 From: jianglinwang Date: Tue, 21 Mar 2023 17:39:25 +0800 Subject: [PATCH 103/251] add ali cost and usage service test. --- .talismanrc | 2 + packages/ali/jest.config.js | 8 +- .../ali/src/__tests__/AliCalculateRow.test.ts | 8 +- .../__tests__/AliCostAndUsageService.test.ts | 113 ++++++++++++++++++ 4 files changed, 124 insertions(+), 7 deletions(-) create mode 100644 packages/ali/src/__tests__/AliCostAndUsageService.test.ts diff --git a/.talismanrc b/.talismanrc index 6a8e1947d..d3c8afe55 100644 --- a/.talismanrc +++ b/.talismanrc @@ -429,6 +429,8 @@ fileignoreconfig: checksum: a1c04f96a9be8e217e535f25a61a5cae1c40933129a05dac81243bb2caa92c60 - filename: packages/ali/src/lib/AliCalculateRow.ts checksum: 77997e1e88c3fe9bbafbda63d4c1c9b83df439417664cf0d080488b01c8c0c51 +- filename: packages/ali/src/__tests__/AliCostAndUsageService.test.ts + checksum: 5f7313007ca6a4eae2a608faa11dbb0be97712371703b618e28093138a345363 scopeconfig: - scope: node version: "1.0" diff --git a/packages/ali/jest.config.js b/packages/ali/jest.config.js index 9c277e411..3e360d3ab 100644 --- a/packages/ali/jest.config.js +++ b/packages/ali/jest.config.js @@ -9,10 +9,10 @@ module.exports = { ...baseConfig, coverageThreshold: { global: { - statements: 0, - branches: 0, //todo: increase coverage, it was 88 before - functions: 0, - lines: 0, + statements: 0.8, + branches: 0.8, //todo: increase coverage, it was 88 before + functions: 0.8, + lines: 0.8, }, }, testPathIgnorePatterns: ['/src/__tests__/fixtures'], diff --git a/packages/ali/src/__tests__/AliCalculateRow.test.ts b/packages/ali/src/__tests__/AliCalculateRow.test.ts index 112747ad0..3b7c37913 100644 --- a/packages/ali/src/__tests__/AliCalculateRow.test.ts +++ b/packages/ali/src/__tests__/AliCalculateRow.test.ts @@ -6,18 +6,20 @@ import AliCalculateRow from '../lib/AliCalculateRow' import { DescribeInstanceBillResponseBodyDataItems } from '@alicloud/bssopenapi20171214' describe('Ali Calculate Row', () => { - it('gets results from getDataForRegions function', async () => { + it('get results from getDataForRegions given ecs', async () => { const item = new DescribeInstanceBillResponseBodyDataItems() item.instanceConfig = 'I/O 优化实例:I/O 优化实例;操作系统位数:64位;实例规格族:ecs.s6;systemdisk_delete_with_instance:true;实例规格:1核 4GB;操作系统的类型:Linux;体检服务:是;地域:成都region;可用区:可用区A;CPU:1核;系统盘种类:增强型SSD云盘;虚拟交换机:vsw-2vcwp5da90fk0ytcgrn5r;网络类型:专有网络;系统盘大小:50GB;系统盘性能level:PL0;实例系列:系列 V;操作系统:m-2vcg1zc8duf3fwq39n9q;内存:4GBMB;是否是按流量计费:按流量计费;挂载点:/dev/xvda;带宽:10240Kbps;管家服务:是' item.region = '西南1(成都)' item.instanceSpec = 'ecs.s6-c1m4.small' item.productCode = 'ecs' + item.servicePeriod = '1' + item.servicePeriodUnit = '月 ' const row = new AliCalculateRow(item) - console.log(row) - expect(row.vCpuHours).toEqual(720) + expect(row.vCpuHours).toEqual(1) expect(row.region).toEqual('西南1(成都)') expect(row.seriesName).toEqual('ecs.s6-c1m4.small') + expect(row.specificationFamily).toEqual('ecs.s6') expect(row.serviceName).toEqual('ecs') }) }) diff --git a/packages/ali/src/__tests__/AliCostAndUsageService.test.ts b/packages/ali/src/__tests__/AliCostAndUsageService.test.ts new file mode 100644 index 000000000..3e17d2641 --- /dev/null +++ b/packages/ali/src/__tests__/AliCostAndUsageService.test.ts @@ -0,0 +1,113 @@ +/* + * © 2021 Thoughtworks, Inc. + */ + +import { + CCFConfig, + EstimationResult, + GroupBy, + setConfig, +} from '@cloud-carbon-footprint/common' +import { + ComputeEstimator, + EmbodiedEmissionsEstimator, + MemoryEstimator, + NetworkingEstimator, + StorageEstimator, + UnknownEstimator, +} from '@cloud-carbon-footprint/core' +import { ALI_CLOUD_CONSTANTS } from '../domain' +import { AliCostAndUsageService } from '../lib' + +jest.mock('@cloud-carbon-footprint/common', () => ({ + ...(jest.requireActual('@cloud-carbon-footprint/common') as Record< + string, + unknown + >), +})) + +const getUsageSpy = jest.spyOn(AliCostAndUsageService.prototype, 'getUsage') + +const DEFAULT_CONFIG: CCFConfig = { + ALI: { + authentication: { + accessKeyId: 'id', + accessKeySecret: 'secret', + }, + }, +} + +describe('Ali Cost And Usage Service', () => { + const startDate = new Date('2020-11-02') + const endDate = new Date('2020-11-07') + const grouping: GroupBy = GroupBy.day + const billAccountName = 'accountName' + const billAccountID = 'id' + beforeEach(() => { + ALI_CLOUD_CONSTANTS.KILOWATT_HOURS_BY_SERVICE_AND_USAGE_UNIT = { + total: {}, + } + + setConfig(DEFAULT_CONFIG) + }) + + it('Returns estimates for Compute', async () => { + ;(getUsageSpy as jest.Mock).mockResolvedValue({ + body: { + data: { + totalCount: 2, + items: [ + { + billAccountID: billAccountID, + billAccountName: billAccountName, + servicePeriod: '12', + servicePeriodUnit: '月', + productCode: 'ecs', + instanceConfig: + 'I/O 优化实例:I/O例;操作系统位数:64位;实例规格族:ecs.g7;systemdisk_delete_with_instance:true;实例规格:2核 8G;操作系统的类型:Linux;体检服务:是;地域:亚太东南 1 (新加坡);可用区:随机分配;CPU:2核;系统;实例系列:ecs-6;操作系统:aliyun_3_x64_20G_alibase_20230110.vhd;内存:8GBMB;是否是按流量计费:按固定带宽;挂载点:/dev/xvda;带宽:5120Kbps;管家服务:是', + instanceSpec: 'ecs.g7.large', + region: '西南1(成都)', + cashAmount: 0, + }, + ], + }, + }, + }) + const aliCostAndUsageService = new AliCostAndUsageService( + new ComputeEstimator(), + new StorageEstimator(ALI_CLOUD_CONSTANTS.SSDCOEFFICIENT), + new StorageEstimator(ALI_CLOUD_CONSTANTS.HDDCOEFFICIENT), + new NetworkingEstimator(ALI_CLOUD_CONSTANTS.NETWORKING_COEFFICIENT), + new MemoryEstimator(ALI_CLOUD_CONSTANTS.MEMORY_COEFFICIENT), + new UnknownEstimator(ALI_CLOUD_CONSTANTS.ESTIMATE_UNKNOWN_USAGE_BY), + new EmbodiedEmissionsEstimator( + ALI_CLOUD_CONSTANTS.SERVER_EXPECTED_LIFESPAN, + ), + ) + const result = await aliCostAndUsageService.getEstimates( + startDate, + endDate, + grouping, + ) + const expectedResult: EstimationResult[] = [ + { + timestamp: new Date('2020-11-02'), + serviceEstimates: [ + { + kilowattHours: 82.84723199999999, + co2e: 0.053956742067341565, + usesAverageCPUConstant: false, + cloudProvider: 'AliCloud', + accountId: billAccountID, + accountName: billAccountName, + serviceName: 'ecs', + cost: 0, + region: '西南1(成都)', + }, + ], + groupBy: grouping, + }, + ] + expect(result).toEqual(expectedResult) + }) +}) From fa6016d7874b3a3a0f4106cdd0093a2d3835fc65 Mon Sep 17 00:00:00 2001 From: jianglinwang Date: Wed, 22 Mar 2023 14:31:35 +0800 Subject: [PATCH 104/251] add function compute service. --- packages/ali/src/lib/AliCalculateRow.ts | 54 +++++++++++++++++++ .../ali/src/lib/AliCostAndUsageService.ts | 12 ++++- 2 files changed, 64 insertions(+), 2 deletions(-) diff --git a/packages/ali/src/lib/AliCalculateRow.ts b/packages/ali/src/lib/AliCalculateRow.ts index 7d0353bd2..2afb2e74a 100644 --- a/packages/ali/src/lib/AliCalculateRow.ts +++ b/packages/ali/src/lib/AliCalculateRow.ts @@ -48,6 +48,9 @@ export default class AliCalculateRow extends BillingDataRow { } private getVCpuHours(usageDetail: DescribeInstanceBillResponseBodyDataItems) { + if (this.serviceName == 'fc') { + return this.getFunctionComputeVcpuHours(usageDetail.instanceConfig) + } const instanceConfig = usageDetail.instanceConfig return ( this.getUsage(usageDetail.servicePeriod, usageDetail.servicePeriodUnit) * @@ -55,16 +58,67 @@ export default class AliCalculateRow extends BillingDataRow { ) } + private getFunctionComputeVcpuHours(instanceConfig: string) { + const vcpuHoursStr = this.getJsonValue('vCPU资源包', instanceConfig)?.split( + 'vCPU*秒', + )[0] + if (vcpuHoursStr == undefined) { + return 0 + } + const resultArr = vcpuHoursStr.split(' ') + if (resultArr.length <= 1) { + return 0 + } else { + const quantity = parseInt(resultArr[0]) + let unitValue = 0 + const unit = resultArr[1] + if (unit == '万') { + unitValue = 10000 + } else { + unitValue = 100000000 + } + return (quantity * unitValue) / 3600 + } + } + private getMemoryHours( usageDetail: DescribeInstanceBillResponseBodyDataItems, ) { const instanceConfig = usageDetail.instanceConfig + if (this.serviceName == 'fc') { + return this.getFunctionComputeMemoryHours(instanceConfig) + } + return ( this.getUsage(usageDetail.servicePeriod, usageDetail.servicePeriodUnit) * this.parseMemory(instanceConfig) ) } + private getFunctionComputeMemoryHours(instanceConfig: string) { + const memoryHoursStr = this.getJsonValue( + '内存资源包', + instanceConfig, + )?.split('GB*秒')[0] + if (memoryHoursStr == undefined) { + return 0 + } + const resultArr = memoryHoursStr.split(' ') + if (resultArr.length <= 1) { + return 0 + } else { + const quantity = parseInt(resultArr[0]) + let unitValue = 0 + const unit = resultArr[1] + if (unit == '万') { + unitValue = 10000 + } else { + unitValue = 100000000 + } + return (quantity * unitValue) / 3600 + } + } + private getGpuHours(usageDetail: DescribeInstanceBillResponseBodyDataItems) { const instanceConfig = usageDetail.instanceConfig return ( diff --git a/packages/ali/src/lib/AliCostAndUsageService.ts b/packages/ali/src/lib/AliCostAndUsageService.ts index 0a2f6b756..f6646e455 100644 --- a/packages/ali/src/lib/AliCostAndUsageService.ts +++ b/packages/ali/src/lib/AliCostAndUsageService.ts @@ -375,7 +375,11 @@ export default class AliCostAndUsageService { replicationFactor: row.replicationFactor, } - let estimate: FootprintEstimate + let estimate: FootprintEstimate = { + timestamp: undefined, + kilowattHours: 0, + co2e: 0, + } if (this.isSSDStorage(row)) estimate = this.ssdStorageEstimator.estimate( [storageUsage], @@ -407,7 +411,11 @@ export default class AliCostAndUsageService { powerUsageEffectiveness: number, emissionsFactors: CloudConstantsEmissionsFactors, ): FootprintEstimate { - let networkingEstimate: FootprintEstimate + let networkingEstimate: FootprintEstimate = { + timestamp: undefined, + kilowattHours: 0, + co2e: 0, + } if (containsAny(NETWORKING_USAGE_TYPES, consumptionDetailRow.usageType)) { const networkingUsage: NetworkingUsage = { timestamp: consumptionDetailRow.timestamp, From 4142c5a78e07a23df7688dbf66cb34f826ccfe04 Mon Sep 17 00:00:00 2001 From: jianglinwang Date: Fri, 24 Mar 2023 10:54:27 +0800 Subject: [PATCH 105/251] add instance specification mapping when get embodied emissions. --- .../ali/src/lib/AliCostAndUsageService.ts | 38 ++++++++-- packages/ali/src/lib/AliTypes.ts | 69 +++++++++++++++++++ 2 files changed, 101 insertions(+), 6 deletions(-) diff --git a/packages/ali/src/lib/AliCostAndUsageService.ts b/packages/ali/src/lib/AliCostAndUsageService.ts index f6646e455..5afda5cc4 100644 --- a/packages/ali/src/lib/AliCostAndUsageService.ts +++ b/packages/ali/src/lib/AliCostAndUsageService.ts @@ -47,8 +47,9 @@ import { UNKNOWN_USAGE_TYPES, SSD_MANAGED_DISKS_STORAGE_GB, STORAGE_USAGE_TYPES, - HDD_MANAGED_DISKS_STORAGE_GB, + HDD_MANAGED_DISKS_STORAGE_GB, INSTANCE_SPECIFICATION_MAPPING, } from './AliTypes' +import * as console from 'console' export default class AliCostAndUsageService { private readonly logger: Logger @@ -119,7 +120,7 @@ export default class AliCostAndUsageService { this.logger.info( 'computeFootprintEstimate:' + - JSON.stringify(computeFootprintEstimate), + JSON.stringify(computeFootprintEstimate), ) this.logger.info( 'memoryFootprintEstimate:' + JSON.stringify(memoryFootprintEstimate), @@ -355,9 +356,34 @@ export default class AliCostAndUsageService { } private getDataFromSeriesName(seriesName: string) { - this.logger.info('getDataFromSeriesName seriesName:' + seriesName) - // 阿里云各个服务的scope 3 emissions 数据拿不到 - return {} + const instanceTypeDetails = seriesName.split('.') + if (instanceTypeDetails.length <= 1) { + return { + instancevCpu: 0, + scopeThreeEmissions: 0, + largestInstancevCpu: 0, + } + } + const instanceSize = instanceTypeDetails[instanceTypeDetails.length - 1] + const instanceFamily = instanceTypeDetails.slice(0, 2).join('.') + + const instancevCpu = + INSTANCE_SPECIFICATION_MAPPING[instanceFamily]?.[instanceSize]?.[0] + + const scopeThreeEmissions = + INSTANCE_SPECIFICATION_MAPPING[instanceFamily]?.[instanceSize]?.[2] + const familyInstanceTypes: number[][] = Object.values( + INSTANCE_SPECIFICATION_MAPPING[instanceFamily] || {}, + ) + + const [largestInstancevCpu] = + familyInstanceTypes[familyInstanceTypes.length - 1] || [] + + return { + instancevCpu, + scopeThreeEmissions, + largestInstancevCpu, + } } private getStorageFootprintEstimate( @@ -460,7 +486,7 @@ export default class AliCostAndUsageService { const unknownConstants: CloudConstants = { kilowattHoursByServiceAndUsageUnit: - ALI_CLOUD_CONSTANTS.KILOWATT_HOURS_BY_SERVICE_AND_USAGE_UNIT, + ALI_CLOUD_CONSTANTS.KILOWATT_HOURS_BY_SERVICE_AND_USAGE_UNIT, } return this.unknownEstimator.estimate( [unknownUsage], diff --git a/packages/ali/src/lib/AliTypes.ts b/packages/ali/src/lib/AliTypes.ts index 1b837000e..88a90c2b8 100644 --- a/packages/ali/src/lib/AliTypes.ts +++ b/packages/ali/src/lib/AliTypes.ts @@ -159,6 +159,75 @@ export const GPU_VIRTUAL_MACHINE_TYPE_PROCESSOR_MAPPING: { 'ecs.gn5i': [COMPUTE_PROCESSOR_TYPES.NVIDIA_TESLA_P4], } +export const INSTANCE_SPECIFICATION_MAPPING: { + [instanceFamily: string]: { [instanceSize: string]: number[] } // [vcpus, memory, scope3 emissions ] +} = { + 'ecs.g8ae': { + large: [2, 8, 1.1], + xlarge: [4, 16, 1.1], + '2xlarge': [8, 32, 1.1], + '4xlarge': [16, 64, 1.1], + '8xlarge': [32, 128, 1.1], + '16xlarge': [64, 256, 1.1], + '32xlarge': [128, 512, 1.1], + }, + 'ecs.g7se': { + large: [2, 8, 1.2], + xlarge: [4, 16, 1.2], + '2xlarge': [8, 32, 1.2], + '3xlarge': [12, 48, 1.2], + '4xlarge': [16, 64, 1.2], + '6xlarge': [24, 96, 1.2], + '8xlarge': [32, 128, 1.2], + '16xlarge': [64, 256, 1.2], + '32xlarge': [128, 512, 1.2], + }, + 'ecs.g7': { + large: [2, 8, 1.1], + xlarge: [4, 16, 1.1], + '2xlarge': [8, 32, 1.1], + '3xlarge': [12, 48, 1.1], + '4xlarge': [16, 64, 1.1], + '6xlarge': [24, 96, 1.1], + '8xlarge': [32, 128, 1.1], + '16xlarge': [64, 256, 1.1], + '32xlarge': [128, 512, 1.1], + }, + 'ecs.g7t': { + large: [2, 8, 1.1], + xlarge: [4, 16, 1.1], + '2xlarge': [8, 32, 1.1], + '3xlarge': [12, 48, 1.1], + '4xlarge': [16, 64, 1.1], + '6xlarge': [24, 96, 1.1], + '8xlarge': [32, 128, 1.1], + '16xlarge': [64, 256, 1.1], + '32xlarge': [128, 512, 1.1], + }, + 'ecs.g7ne': { + large: [2, 8, 1.1], + xlarge: [4, 16, 1.1], + '2xlarge': [8, 32, 1.1], + '4xlarge': [16, 64, 1.1], + '6xlarge': [24, 96, 1.1], + '8xlarge': [32, 128, 1.1], + '12xlarge': [48, 192, 1.1], + '16xlarge': [64, 256, 1.1], + '32xlarge': [128, 512, 1.1], + }, + 'ecs.g6': { + large: [2, 8, 1.1], + xlarge: [4, 16, 1.1], + '2xlarge': [8, 32, 1.1], + '3xlarge': [12, 48, 1.1], + '4xlarge': [16, 64, 1.1], + '6xlarge': [24, 96, 1.1], + '8xlarge': [32, 128, 1.1], + '13xlarge': [52, 192, 1.1], + '26xlarge': [104, 384, 1.1], + }, +} + export const SSD_MANAGED_DISKS_STORAGE_GB: { [diskType: string]: number } = {} From a898f4ea4fd758ec00dfc8dd8c9a36fb93a20cc9 Mon Sep 17 00:00:00 2001 From: Melanie Hahn Date: Thu, 25 May 2023 11:22:28 -0700 Subject: [PATCH 106/251] Swagger WIP [Melanie Hahn] --- package.json | 3 + packages/api/package.json | 6 +- packages/api/src/api.ts | 146 +++++++++++++++++++- packages/api/src/server.ts | 9 +- packages/api/src/swagger.ts | 36 +++++ packages/api/tsconfig.json | 3 +- packages/common/src/EmissionRatioResult.ts | 15 ++ packages/common/src/EstimationResult.ts | 41 ++++++ packages/common/src/RecommendationResult.ts | 80 +++++++++++ 9 files changed, 333 insertions(+), 6 deletions(-) create mode 100644 packages/api/src/swagger.ts diff --git a/package.json b/package.json index 52507fe06..55add261f 100644 --- a/package.json +++ b/package.json @@ -57,5 +57,8 @@ "release": "changeset version && yarn install", "postinstall": "husky install", "version": "yarn changeset version && yarn install --no-immutable" + }, + "dependencies": { + "swagger-jsdoc": "^6.2.8" } } diff --git a/packages/api/package.json b/packages/api/package.json index 5efbeaba1..3232eebb2 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -51,6 +51,8 @@ "@types/node": "^17.0.8", "@types/source-map-support": "^0.5.3", "@types/supertest": "^2.0.11", + "@types/swagger-jsdoc": "^6.0.1", + "@types/swagger-ui-express": "^4.1.3", "@typescript-eslint/eslint-plugin": "^5.9.0", "@typescript-eslint/parser": "^5.9.0", "eslint": "^8.6.0", @@ -77,7 +79,9 @@ "cors": "^2.8.5", "express": "^4.17.1", "helmet": "^5.0.1", - "module-alias": "^2.2.2" + "module-alias": "^2.2.2", + "swagger-jsdoc": "^6.2.8", + "swagger-ui-express": "^4.6.2" }, "lint-staged": { "*.{js,ts}": [ diff --git a/packages/api/src/api.ts b/packages/api/src/api.ts index faaf1313e..a821cb3d6 100644 --- a/packages/api/src/api.ts +++ b/packages/api/src/api.ts @@ -124,10 +124,154 @@ export const createRouter = (config?: CCFConfig) => { setConfig(config) const router = express.Router() - + /** + * @openapi + * /api/footprint: + * get: + * tags: + * - Footprint + * summary: Get the footprint for the date range + * parameters: + * - name: start + * in: query + * description: The start date for the footprint; e.g. 2022-10-18 + * schema: + * type: string + * required: true + * - name: end + * in: query + * schema: + * type: string + * description: The end date for the footprint + * required: true + * - name: ignoreCache + * in: query + * schema: + * type: boolean + * default: false + * required: false + * - name: groupBy + * in: query + * schema: + * type: string + * default: day + * required: false + * - name: limit + * in: query + * schema: + * type: number + * default: 50000 + * description: The maximum number of estimates to return + * required: false + * - name: skip + * in: query + * schema: + * type: number + * default: 0 + * description: The number of estimates to skip over + * required: false + * - name: cloudProviders + * in: query + * schema: + * type: array + * items: + * type: string + * default: [] + * description: Can be used to filter estimates + * required: false + * - name: accounts + * in: query + * schema: + * type: array + * items: + * type: string + * default: [] + * description: Can be used to filter estimates + * required: false + * - name: services + * in: query + * schema: + * type: array + * items: + * type: string + * default: [] + * description: Can be used to filter estimates + * required: false + * - name: regions + * in: query + * schema: + * type: array + * items: + * type: string + * default: [] + * description: Can be used to filter estimates + * required: false + * - name: tags + * in: query + * schema: + * type: object + * additionalProperties: + * type: string + * default: {} + * description: Can be used to filter estimates + * required: false + * responses: + * 200: + * description: Success + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/FootprintResponse' + * 400: + * description: Bad request + */ router.get('/footprint', FootprintApiMiddleware) + + /** + * @openapi + * /api/regions/emissions-factors: + * get: + * tags: + * - Emissions Factors + * description: Gives you back the emissions factors for all regions? + * responses: + * 200: + * description: Success + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/EmissionResponse' + */ router.get('/regions/emissions-factors', EmissionsApiMiddleware) + + /** + * @openapi + * /api/recommendations: + * get: + * tags: + * - Recommendations + * description: Gives you back recommendations to decrease your cloud carbon footprint + * responses: + * 200: + * description: Success + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/RecommendationsResponse' + */ router.get('/recommendations', RecommendationsApiMiddleware) + + /** + * @openapi + * /api/healthz: + * get: + * tags: + * - Healthcheck + * description: Responds if the app is up and running + * responses: + * 200: + * description: App is up and running + */ router.get('/healthz', (req: express.Request, res: express.Response) => { res.status(200).send('OK') }) diff --git a/packages/api/src/server.ts b/packages/api/src/server.ts index 8559303f8..f2c77d3ea 100644 --- a/packages/api/src/server.ts +++ b/packages/api/src/server.ts @@ -13,6 +13,7 @@ import cors, { CorsOptions } from 'cors' import { createRouter } from './api' import auth from './auth' import { Logger } from '@cloud-carbon-footprint/common' +import swaggerDocs from './swagger' const port = process.env.PORT || 4000 const httpApp = express() @@ -41,8 +42,10 @@ if (process.env.ENABLE_CORS) { httpApp.use('/api', createRouter()) -httpApp.listen(port, () => +httpApp.listen(port, () => { serverLogger.info( `Cloud Carbon Footprint Server listening at http://localhost:${port}`, - ), -) + ) + + swaggerDocs(httpApp, Number(port)) +}) diff --git a/packages/api/src/swagger.ts b/packages/api/src/swagger.ts new file mode 100644 index 000000000..01a8c5e94 --- /dev/null +++ b/packages/api/src/swagger.ts @@ -0,0 +1,36 @@ +import { Express, Request, Response } from 'express' +import swaggerJsdoc from 'swagger-jsdoc' +import swaggerUi from 'swagger-ui-express' +// import {version} from '../package.json' +import { Logger } from '@cloud-carbon-footprint/common' +const serverLogger = new Logger('server') + +const version = '1.6.0' + +const options: swaggerJsdoc.Options = { + definition: { + openapi: '3.0.0', + info: { + title: 'CCF API Docs', + version, + }, + }, + apis: ['./src/api.ts', '../common/src/*.ts'], +} + +const swaggerSpec = swaggerJsdoc(options) + +function swaggerDocs(app: Express, port: number) { + // Swagger page + app.use('/docs', swaggerUi.serve, swaggerUi.setup(swaggerSpec)) + + // Docs in JSON format + app.get('/docs.json', (req: Request, res: Response) => { + res.setHeader('Content-Type', 'application/json') + res.send(swaggerSpec) + }) + + serverLogger.info(`Docs available at http://localhost:${port}/docs`) +} + +export default swaggerDocs diff --git a/packages/api/tsconfig.json b/packages/api/tsconfig.json index 8835941ef..ac8cce504 100644 --- a/packages/api/tsconfig.json +++ b/packages/api/tsconfig.json @@ -4,7 +4,8 @@ "rootDir": "./src", "baseUrl": ".", "outDir": "./dist", - "skipLibCheck": true + "skipLibCheck": true, + "resolveJsonModule": true }, "include": ["./src"], } diff --git a/packages/common/src/EmissionRatioResult.ts b/packages/common/src/EmissionRatioResult.ts index 7f0def962..29340a904 100644 --- a/packages/common/src/EmissionRatioResult.ts +++ b/packages/common/src/EmissionRatioResult.ts @@ -2,6 +2,21 @@ * © 2021 Thoughtworks, Inc. */ +/** + * @openapi + * components: + * schemas: + * EmissionResponse: + * type: object + * properties: + * cloudProvider: + * type: string + * region: + * type: string + * mtPerKwHour: + * type: number + * description: metric ton co2e per kwHour + */ export type EmissionRatioResult = { cloudProvider: string region: string diff --git a/packages/common/src/EstimationResult.ts b/packages/common/src/EstimationResult.ts index 72cd676a1..6bf72fbcc 100644 --- a/packages/common/src/EstimationResult.ts +++ b/packages/common/src/EstimationResult.ts @@ -6,6 +6,47 @@ import { reduceBy } from 'ramda' import { GroupBy } from './Config' import { getPeriodEndDate } from './helpers' +/** + * @openapi + * components: + * schemas: + * FootprintResponse: + * type: object + * properties: + * timestamp: + * type: string + * serviceEstimates: + * type: array + * items: + * type: object + * properties: + * cloudProvider: + * type: string + * kilowattHours: + * type: number + * co2e: + * type: number + * cost: + * type: number + * usesAverageCPUConstant?: + * type: boolean + * accountId: + * type: string + * accountName: + * type: string + * serviceName: + * type: string + * region: + * type: string + * tags: + * type: array + * periodStartDate: + * type: string + * periodEndDate: + * type: string + * groupBy: + * type: string + */ export interface EstimationResult { readonly timestamp: Date readonly serviceEstimates: ServiceData[] diff --git a/packages/common/src/RecommendationResult.ts b/packages/common/src/RecommendationResult.ts index 523dc0cdc..6117e4e69 100644 --- a/packages/common/src/RecommendationResult.ts +++ b/packages/common/src/RecommendationResult.ts @@ -2,6 +2,40 @@ * © 2021 Thoughtworks, Inc. */ +/** + * @openapi + * components: + * schemas: + * RecommendationsResponse: + * type: object + * properties: + * cloudProvider: + * type: string + * description: aws, gcp, ... + * accountId: + * type: string + * accountName: + * type: string + * region: + * type: string + * recommendationType: + * type: string + * recommendationDetail?: + * type: string + * resourceId?: + * type: string + * instanceName?: + * type: string + * kilowattHourSavings: + * type: number + * costSavings: + * type: number + * co2eSavings: + * type: number + * recommendationOptions?: + * type: object + * description: Either EC2, EBS, or Lambda recommendation + */ export interface RecommendationResult { readonly cloudProvider: string readonly accountId: string @@ -22,6 +56,22 @@ export type ComputeOptimizerRecommendationOption = | EBSRecommendationOption | LambdaRecommendationOption +/** + * @openapi + * components: + * schemas: + * EC2Recommendation: + * type: object + * properties: + * instanceType: + * type: string + * costSavings: + * type: string + * performanceRisk: + * type: string + * vcpus: + * type: string + */ export type EC2RecommendationOption = { instanceType: string costSavings: string @@ -29,6 +79,22 @@ export type EC2RecommendationOption = { vcpus: string } +/** + * @openapi + * components: + * schemas: + * EBSRecommendation: + * type: object + * properties: + * volumeType: + * type: string + * volumeSize: + * type: string + * costSavings: + * type: string + * performanceRisk: + * type: string + */ export type EBSRecommendationOption = { volumeType: string volumeSize: string @@ -36,6 +102,20 @@ export type EBSRecommendationOption = { performanceRisk: string } +/** + * @openapi + * components: + * schemas: + * LambdaRecommendation: + * type: object + * properties: + * memorySize: + * type: string + * costSavings: + * type: string + * performanceRisk?: + * type: string + */ export type LambdaRecommendationOption = { memorySize: string costSavings: string From 58648733279f0fa90b54f53c9da5be991b9c28fd Mon Sep 17 00:00:00 2001 From: huahuajiujiu Date: Mon, 29 May 2023 15:01:22 +0800 Subject: [PATCH 107/251] Revert the change for adding ali cloud for the pull request. --- packages/app/src/App.ts | 19 +++---------------- 1 file changed, 3 insertions(+), 16 deletions(-) diff --git a/packages/app/src/App.ts b/packages/app/src/App.ts index e79a0b5c9..ac45dfe02 100644 --- a/packages/app/src/App.ts +++ b/packages/app/src/App.ts @@ -30,7 +30,6 @@ import { OnPremise } from '@cloud-carbon-footprint/on-premise' import cache from './Cache' import { EstimationRequest, RecommendationRequest } from './CreateValidRequest' import { includeCloudProviders } from './common/helpers' -import { ALIAccount } from '@cloud-carbon-footprint/ali' export const recommendationsMockPath = 'recommendations.mock.json' @@ -44,7 +43,7 @@ export default class App { const grouping = request.groupBy as GroupBy const config = configLoader() includeCloudProviders(cloudProviderToSeed, config) - const { AWS, GCP, AZURE, ALI } = config + const { AWS, GCP, AZURE } = config if (process.env.TEST_MODE) { return [] } @@ -114,24 +113,12 @@ export default class App { AzureEstimatesByRegion.push(estimates) appLogger.info('Finished Azure Estimations') } - const AliEstimatesByRegion: EstimationResult[][] = [] - if (ALI?.INCLUDE_ESTIMATES && ALI?.USE_BILLING_DATA) { - appLogger.info('Starting Ali Estimations') - const aliAccount = new ALIAccount() - const estimates = await aliAccount.getDataFromCostAndUsageReports( - startDate, - endDate, - grouping, - ) - AliEstimatesByRegion.push(estimates) - appLogger.info('Finished Ali Estimations') - } + return reduceByTimestamp( AWSEstimatesByRegion.flat() .flat() .concat(GCPEstimatesByRegion.flat()) - .concat(AzureEstimatesByRegion.flat()) - .concat(AliEstimatesByRegion.flat()), + .concat(AzureEstimatesByRegion.flat()), ) } From d568da2a66fa7468e161a5d9d6cda9d991a0e23f Mon Sep 17 00:00:00 2001 From: huahuajiujiu Date: Mon, 29 May 2023 15:10:02 +0800 Subject: [PATCH 108/251] Fix the test and update the yarn lock. --- .../src/__tests__/AliCostAndUsageService.test.ts | 4 ++-- packages/ali/src/lib/AliCostAndUsageService.ts | 8 ++++---- yarn.lock | 16 ++++++++++++++++ 3 files changed, 22 insertions(+), 6 deletions(-) diff --git a/packages/ali/src/__tests__/AliCostAndUsageService.test.ts b/packages/ali/src/__tests__/AliCostAndUsageService.test.ts index 3e17d2641..0864040d5 100644 --- a/packages/ali/src/__tests__/AliCostAndUsageService.test.ts +++ b/packages/ali/src/__tests__/AliCostAndUsageService.test.ts @@ -94,8 +94,8 @@ describe('Ali Cost And Usage Service', () => { timestamp: new Date('2020-11-02'), serviceEstimates: [ { - kilowattHours: 82.84723199999999, - co2e: 0.053956742067341565, + kilowattHours: 108.87606106987849, + co2e: 0.0709087968618621, usesAverageCPUConstant: false, cloudProvider: 'AliCloud', accountId: billAccountID, diff --git a/packages/ali/src/lib/AliCostAndUsageService.ts b/packages/ali/src/lib/AliCostAndUsageService.ts index 5afda5cc4..5079f4ea3 100644 --- a/packages/ali/src/lib/AliCostAndUsageService.ts +++ b/packages/ali/src/lib/AliCostAndUsageService.ts @@ -47,9 +47,9 @@ import { UNKNOWN_USAGE_TYPES, SSD_MANAGED_DISKS_STORAGE_GB, STORAGE_USAGE_TYPES, - HDD_MANAGED_DISKS_STORAGE_GB, INSTANCE_SPECIFICATION_MAPPING, + HDD_MANAGED_DISKS_STORAGE_GB, + INSTANCE_SPECIFICATION_MAPPING, } from './AliTypes' -import * as console from 'console' export default class AliCostAndUsageService { private readonly logger: Logger @@ -120,7 +120,7 @@ export default class AliCostAndUsageService { this.logger.info( 'computeFootprintEstimate:' + - JSON.stringify(computeFootprintEstimate), + JSON.stringify(computeFootprintEstimate), ) this.logger.info( 'memoryFootprintEstimate:' + JSON.stringify(memoryFootprintEstimate), @@ -486,7 +486,7 @@ export default class AliCostAndUsageService { const unknownConstants: CloudConstants = { kilowattHoursByServiceAndUsageUnit: - ALI_CLOUD_CONSTANTS.KILOWATT_HOURS_BY_SERVICE_AND_USAGE_UNIT, + ALI_CLOUD_CONSTANTS.KILOWATT_HOURS_BY_SERVICE_AND_USAGE_UNIT, } return this.unknownEstimator.estimate( [unknownUsage], diff --git a/yarn.lock b/yarn.lock index dc6977ecf..8bd654ccd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -15406,6 +15406,15 @@ __metadata: languageName: node linkType: hard +"kitx@npm:^2.0.0, kitx@npm:^2.1.0": + version: 2.1.0 + resolution: "kitx@npm:2.1.0" + dependencies: + "@types/node": ^12.0.2 + checksum: 066837ecea4558bffe4fd01a7df93ed38dbd495d0b89a575c0ee28864ff2176a6e1354826340bac223cc5909241a3460b4cecd463d6a93c5d2fd86def73c2870 + languageName: node + linkType: hard + "klaw@npm:^3.0.0": version: 3.0.0 resolution: "klaw@npm:3.0.0" @@ -19395,6 +19404,13 @@ __metadata: languageName: node linkType: hard +"ramda@npm:^0.28.0": + version: 0.28.0 + resolution: "ramda@npm:0.28.0" + checksum: 44ea6e5010bba70151b6a92d8114a91915e8b5a16105cce65fae58c9d7386b812c429645e35f21141d7087568550ce383bc10ee1a65cdec951f4b69ea457e6a4 + languageName: node + linkType: hard + "ramda@npm:^0.29.0": version: 0.29.0 resolution: "ramda@npm:0.29.0" From d24cc610840ce48ae9d4a502622dc47d5171459c Mon Sep 17 00:00:00 2001 From: pheianox <77569421+pheianox@users.noreply.github.com> Date: Tue, 23 May 2023 11:37:51 -0400 Subject: [PATCH 109/251] Add PITS Global Data Recovery Services to the list of adopters This pull request adds new entry in the adopters list --- ADOPTERS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/ADOPTERS.md b/ADOPTERS.md index 764525cf2..4e6a7c49e 100644 --- a/ADOPTERS.md +++ b/ADOPTERS.md @@ -4,3 +4,4 @@ | [OSP](https://www.osp.de/en) | ccf@osp.de | Measure and reduce cloud carbon emissions for OSP. The tool is part of the tool infrastructure of the OSP Sustainable Programming Initiative (SPI). The tool is part of a strategy to become more energy efficient with our cloud projects. | |[Climatiq](https://www.climatiq.io/) | hello@climatiq.io | Measure the embodied and use-phase cloud emissions of GCP, AZURE, and AWS. | | | | | +|[PITS Global Data Recovery Services](https://www.pitsdatarecovery.net/) | @pheianox | Analytics and effective integration with other cloud providers | From 5e6a435119384fc29a3003e78bbc97961fdc3673 Mon Sep 17 00:00:00 2001 From: Arik Smith Date: Tue, 30 May 2023 10:53:09 -0400 Subject: [PATCH 110/251] add github link to latest adopter --- ADOPTERS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ADOPTERS.md b/ADOPTERS.md index 4e6a7c49e..dd885ff21 100644 --- a/ADOPTERS.md +++ b/ADOPTERS.md @@ -4,4 +4,4 @@ | [OSP](https://www.osp.de/en) | ccf@osp.de | Measure and reduce cloud carbon emissions for OSP. The tool is part of the tool infrastructure of the OSP Sustainable Programming Initiative (SPI). The tool is part of a strategy to become more energy efficient with our cloud projects. | |[Climatiq](https://www.climatiq.io/) | hello@climatiq.io | Measure the embodied and use-phase cloud emissions of GCP, AZURE, and AWS. | | | | | -|[PITS Global Data Recovery Services](https://www.pitsdatarecovery.net/) | @pheianox | Analytics and effective integration with other cloud providers | +|[PITS Global Data Recovery Services](https://www.pitsdatarecovery.net/) | [@pheianox](https://github.com/pheianox) | Analytics and effective integration with other cloud providers | From 0d20b32c8581b9227625c1c7aba5798b4ebf1104 Mon Sep 17 00:00:00 2001 From: Arik Smith Date: Thu, 1 Jun 2023 19:38:13 -0400 Subject: [PATCH 111/251] [changset] Common: Adds support for Google Cloud resource tags to configuration options --- .changeset/large-tables-wave.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/large-tables-wave.md diff --git a/.changeset/large-tables-wave.md b/.changeset/large-tables-wave.md new file mode 100644 index 000000000..f30cefde4 --- /dev/null +++ b/.changeset/large-tables-wave.md @@ -0,0 +1,5 @@ +--- +'@cloud-carbon-footprint/common': minor +--- + +Adds support for Google Cloud resource tags to configuration options From 4be98c4c47aad1bd70c595ad0d9846f3fb5f1344 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 1 Jun 2023 23:40:30 +0000 Subject: [PATCH 112/251] Version Packages --- .changeset/hungry-spiders-unite.md | 5 ----- .changeset/large-tables-wave.md | 5 ----- packages/app/CHANGELOG.md | 9 +++++++++ packages/app/package.json | 6 +++--- packages/cli/CHANGELOG.md | 8 ++++++++ packages/cli/package.json | 6 +++--- packages/common/CHANGELOG.md | 6 ++++++ packages/common/package.json | 2 +- packages/gcp/CHANGELOG.md | 11 +++++++++++ packages/gcp/package.json | 4 ++-- yarn.lock | 16 ++++++++-------- 11 files changed, 51 insertions(+), 27 deletions(-) delete mode 100644 .changeset/hungry-spiders-unite.md delete mode 100644 .changeset/large-tables-wave.md diff --git a/.changeset/hungry-spiders-unite.md b/.changeset/hungry-spiders-unite.md deleted file mode 100644 index 1d7edcd82..000000000 --- a/.changeset/hungry-spiders-unite.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@cloud-carbon-footprint/gcp': minor ---- - -Updates emissions factors and PUE constants to latest published values diff --git a/.changeset/large-tables-wave.md b/.changeset/large-tables-wave.md deleted file mode 100644 index f30cefde4..000000000 --- a/.changeset/large-tables-wave.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@cloud-carbon-footprint/common': minor ---- - -Adds support for Google Cloud resource tags to configuration options diff --git a/packages/app/CHANGELOG.md b/packages/app/CHANGELOG.md index 0bbb8f878..f4bedbbe8 100644 --- a/packages/app/CHANGELOG.md +++ b/packages/app/CHANGELOG.md @@ -1,5 +1,14 @@ # @cloud-carbon-footprint/app +## 1.2.2 + +### Patch Changes + +- Updated dependencies [42b2359e] +- Updated dependencies [0d20b32c] + - @cloud-carbon-footprint/gcp@0.13.0 + - @cloud-carbon-footprint/common@1.11.0 + ## 1.2.1 ### Patch Changes diff --git a/packages/app/package.json b/packages/app/package.json index 091fce5cf..3da047a48 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -1,6 +1,6 @@ { "name": "@cloud-carbon-footprint/app", - "version": "1.2.1", + "version": "1.2.2", "license": "Apache-2.0", "description": "The logic to bootstrap the cloud-carbon-footprint server-side application", "main": "src/index.ts", @@ -42,8 +42,8 @@ "dependencies": { "@cloud-carbon-footprint/aws": "^0.14.4", "@cloud-carbon-footprint/azure": "^1.3.0", - "@cloud-carbon-footprint/common": "^1.10.0", - "@cloud-carbon-footprint/gcp": "^0.12.0", + "@cloud-carbon-footprint/common": "^1.11.0", + "@cloud-carbon-footprint/gcp": "^0.13.0", "@cloud-carbon-footprint/on-premise": "^0.1.1", "@google-cloud/storage": "^5.16.1", "@sovpro/delimited-stream": "^1.1.0", diff --git a/packages/cli/CHANGELOG.md b/packages/cli/CHANGELOG.md index 905406d0b..a7f093089 100644 --- a/packages/cli/CHANGELOG.md +++ b/packages/cli/CHANGELOG.md @@ -1,5 +1,13 @@ # @cloud-carbon-footprint/cli +## 1.10.2 + +### Patch Changes + +- Updated dependencies [0d20b32c] + - @cloud-carbon-footprint/common@1.11.0 + - @cloud-carbon-footprint/app@1.2.2 + ## 1.10.1 ### Patch Changes diff --git a/packages/cli/package.json b/packages/cli/package.json index 46f52875d..01a854769 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@cloud-carbon-footprint/cli", - "version": "1.10.1", + "version": "1.10.2", "license": "Apache-2.0", "description": "Command Line Interface as an entrypoint to get cloud energy and carbon emissions.", "main": "src/index.ts", @@ -70,8 +70,8 @@ "dependencies": { "@azure/arm-consumption": "^9.2.0", "@azure/arm-resources-subscriptions": "^2.0.2", - "@cloud-carbon-footprint/app": "1.2.1", - "@cloud-carbon-footprint/common": "^1.10.0", + "@cloud-carbon-footprint/app": "1.2.2", + "@cloud-carbon-footprint/common": "^1.11.0", "@types/cli-table": "^0.3.0", "@types/prompts": "^2.0.12", "@types/ramda": "^0.29.0", diff --git a/packages/common/CHANGELOG.md b/packages/common/CHANGELOG.md index c239518c3..27829dbd0 100644 --- a/packages/common/CHANGELOG.md +++ b/packages/common/CHANGELOG.md @@ -1,5 +1,11 @@ # @cloud-carbon-footprint/common +## 1.11.0 + +### Minor Changes + +- 0d20b32c: Adds support for Google Cloud resource tags to configuration options + ## 1.10.0 ### Minor Changes diff --git a/packages/common/package.json b/packages/common/package.json index ae9f4a4a4..3086c8a9c 100644 --- a/packages/common/package.json +++ b/packages/common/package.json @@ -1,6 +1,6 @@ { "name": "@cloud-carbon-footprint/common", - "version": "1.10.0", + "version": "1.11.0", "license": "Apache-2.0", "description": "Common functionality to be shared among other cloud carbon footprint packages", "main": "src/index.ts", diff --git a/packages/gcp/CHANGELOG.md b/packages/gcp/CHANGELOG.md index b7ceb5647..0a027d92c 100644 --- a/packages/gcp/CHANGELOG.md +++ b/packages/gcp/CHANGELOG.md @@ -1,5 +1,16 @@ # @cloud-carbon-footprint/gcp +## 0.13.0 + +### Minor Changes + +- 42b2359e: Updates emissions factors and PUE constants to latest published values + +### Patch Changes + +- Updated dependencies [0d20b32c] + - @cloud-carbon-footprint/common@1.11.0 + ## 0.12.0 ### Minor Changes diff --git a/packages/gcp/package.json b/packages/gcp/package.json index 07b3581d0..d0074ccf1 100644 --- a/packages/gcp/package.json +++ b/packages/gcp/package.json @@ -1,6 +1,6 @@ { "name": "@cloud-carbon-footprint/gcp", - "version": "0.12.0", + "version": "0.13.0", "license": "Apache-2.0", "description": "The core logic to get cloud usage data and estimate energy and carbon emissions from Google Cloud Platform.", "main": "src/index.ts", @@ -41,7 +41,7 @@ "lint:fix": "eslint '*/**/*.ts' --quiet --fix" }, "dependencies": { - "@cloud-carbon-footprint/common": "^1.9.0", + "@cloud-carbon-footprint/common": "^1.11.0", "@cloud-carbon-footprint/core": "^0.17.3", "@google-cloud/bigquery": "^5.9.3", "@google-cloud/compute": "^3.9.0", diff --git a/yarn.lock b/yarn.lock index 9a9aed373..2c0f70a45 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2242,14 +2242,14 @@ __metadata: languageName: unknown linkType: soft -"@cloud-carbon-footprint/app@1.2.1, @cloud-carbon-footprint/app@^1.2.1, @cloud-carbon-footprint/app@workspace:packages/app": +"@cloud-carbon-footprint/app@1.2.2, @cloud-carbon-footprint/app@^1.2.1, @cloud-carbon-footprint/app@workspace:packages/app": version: 0.0.0-use.local resolution: "@cloud-carbon-footprint/app@workspace:packages/app" dependencies: "@cloud-carbon-footprint/aws": ^0.14.4 "@cloud-carbon-footprint/azure": ^1.3.0 - "@cloud-carbon-footprint/common": ^1.10.0 - "@cloud-carbon-footprint/gcp": ^0.12.0 + "@cloud-carbon-footprint/common": ^1.11.0 + "@cloud-carbon-footprint/gcp": ^0.13.0 "@cloud-carbon-footprint/on-premise": ^0.1.1 "@google-cloud/storage": ^5.16.1 "@sovpro/delimited-stream": ^1.1.0 @@ -2355,8 +2355,8 @@ __metadata: dependencies: "@azure/arm-consumption": ^9.2.0 "@azure/arm-resources-subscriptions": ^2.0.2 - "@cloud-carbon-footprint/app": 1.2.1 - "@cloud-carbon-footprint/common": ^1.10.0 + "@cloud-carbon-footprint/app": 1.2.2 + "@cloud-carbon-footprint/common": ^1.11.0 "@types/cli-table": ^0.3.0 "@types/jest": ^27.4.0 "@types/jest-when": ^3.5.0 @@ -2458,7 +2458,7 @@ __metadata: languageName: unknown linkType: soft -"@cloud-carbon-footprint/common@^1.10.0, @cloud-carbon-footprint/common@^1.8.0, @cloud-carbon-footprint/common@^1.9.0, @cloud-carbon-footprint/common@workspace:packages/common": +"@cloud-carbon-footprint/common@^1.10.0, @cloud-carbon-footprint/common@^1.11.0, @cloud-carbon-footprint/common@^1.8.0, @cloud-carbon-footprint/common@^1.9.0, @cloud-carbon-footprint/common@workspace:packages/common": version: 0.0.0-use.local resolution: "@cloud-carbon-footprint/common@workspace:packages/common" dependencies: @@ -2548,11 +2548,11 @@ __metadata: languageName: unknown linkType: soft -"@cloud-carbon-footprint/gcp@^0.12.0, @cloud-carbon-footprint/gcp@workspace:packages/gcp": +"@cloud-carbon-footprint/gcp@^0.13.0, @cloud-carbon-footprint/gcp@workspace:packages/gcp": version: 0.0.0-use.local resolution: "@cloud-carbon-footprint/gcp@workspace:packages/gcp" dependencies: - "@cloud-carbon-footprint/common": ^1.9.0 + "@cloud-carbon-footprint/common": ^1.11.0 "@cloud-carbon-footprint/core": ^0.17.3 "@google-cloud/bigquery": ^5.9.3 "@google-cloud/compute": ^3.9.0 From 2f280fc624d52039ecdddb7464079d5f724f3498 Mon Sep 17 00:00:00 2001 From: Tom Kennes Date: Tue, 30 May 2023 18:05:02 +0200 Subject: [PATCH 113/251] adding additional switch case statement --- .talismanrc | 2 ++ packages/azure/package.json | 2 +- packages/azure/src/application/AzureCredentialsProvider.ts | 7 ++++++- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/.talismanrc b/.talismanrc index bd35f7729..ff6cfe214 100644 --- a/.talismanrc +++ b/.talismanrc @@ -173,6 +173,8 @@ fileignoreconfig: checksum: aaae306bc2bf8a39f34e31f41e7e7c9fd98321fcefc72aca0e126500446ccbfb - filename: packages/azure/src/__tests__/AzureCredentialsProvider.test.ts checksum: 4719d9bfa50789868066ca218f2202a316aaa70904a4152c2a5dfde80f11ba80 +- filename: packages/azure/src/application/AzureCredentialsProvider.ts + checksum: 08ea921d8cf6e05dd55183b8c9c338dee64bcb3a77d875c276455f79e3034c4e - filename: packages/azure/src/__tests__/ConsumptionManagement.test.ts checksum: 68f9a8c23d08e892511e99356f39c010b8e8959fd012aaa51e999cd51e51181b - filename: packages/azure/src/lib/ConsumptionManagement.ts diff --git a/packages/azure/package.json b/packages/azure/package.json index a0f2efeef..54f55d37e 100644 --- a/packages/azure/package.json +++ b/packages/azure/package.json @@ -44,7 +44,7 @@ "@azure/arm-advisor": "^3.1.0", "@azure/arm-consumption": "^9.2.0", "@azure/arm-resources-subscriptions": "^2.0.2", - "@azure/identity": "^3.1.4", + "@azure/identity": "^3.2.2", "@azure/ms-rest-js": "^2.6.6", "@azure/ms-rest-nodeauth": "^3.1.1", "@cloud-carbon-footprint/common": "^1.10.0", diff --git a/packages/azure/src/application/AzureCredentialsProvider.ts b/packages/azure/src/application/AzureCredentialsProvider.ts index b411e382d..5cdbd1250 100644 --- a/packages/azure/src/application/AzureCredentialsProvider.ts +++ b/packages/azure/src/application/AzureCredentialsProvider.ts @@ -2,7 +2,7 @@ * © 2021 Thoughtworks, Inc. */ -import { ClientSecretCredential } from '@azure/identity' +import { ClientSecretCredential, WorkloadIdentityCredential } from '@azure/identity' import { SecretManagerServiceClient } from '@google-cloud/secret-manager' import { configLoader } from '@cloud-carbon-footprint/common' @@ -23,6 +23,11 @@ export default class AzureCredentialsProvider { clientIdFromGoogle, clientSecretFromGoogle, ) + case 'workload_identity': + return new WorkloadIdentityCredential({ + tenantId: tenantId, + clientId: clientId + }) default: return new ClientSecretCredential(tenantId, clientId, clientSecret) } From 6403a608cc2982624e70fcfc2928a2b652f67364 Mon Sep 17 00:00:00 2001 From: Tom Kennes Date: Wed, 31 May 2023 09:54:19 +0200 Subject: [PATCH 114/251] updating lockfile --- .husky/pre-commit | 2 ++ yarn.lock | 10 +++++----- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/.husky/pre-commit b/.husky/pre-commit index 383189262..36e0313a6 100755 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -9,6 +9,8 @@ if [[ "$branch" != "changeset-release/trunk" ]]; then export TALISMAN_INTERACTIVE="false" fi + ls -alh $TALISMAN_HOME + ./scripts/copyright_check.sh && ./scripts/branch_warning.sh && $TALISMAN_HOME/talisman_hook_script pre-commit || exit 1 if [[ "$root" -gt "0" ]]; then echo 'Detecting modified files in packages directory...' diff --git a/yarn.lock b/yarn.lock index 2c0f70a45..c07444f63 100644 --- a/yarn.lock +++ b/yarn.lock @@ -180,9 +180,9 @@ __metadata: languageName: node linkType: hard -"@azure/identity@npm:^3.1.4": - version: 3.1.4 - resolution: "@azure/identity@npm:3.1.4" +"@azure/identity@npm:^3.2.2": + version: 3.2.2 + resolution: "@azure/identity@npm:3.2.2" dependencies: "@azure/abort-controller": ^1.0.0 "@azure/core-auth": ^1.3.0 @@ -200,7 +200,7 @@ __metadata: stoppable: ^1.1.0 tslib: ^2.2.0 uuid: ^8.3.0 - checksum: 8851b32623565235ca390e8db340a7320864267c552a44d5e564db126d0dc7020b7de787914e50578b720ed80b99c87e957dfe302400cd50d64367e33ee294a0 + checksum: cbedd293ed6360d98c87148686a1be3a38aad3eaeadcaa7d73e79469904aac2f949a1be67e868d3fd295e749a24ad91bc389ddb82d8574dd173c0583767ec6bf languageName: node linkType: hard @@ -2318,7 +2318,7 @@ __metadata: "@azure/arm-advisor": ^3.1.0 "@azure/arm-consumption": ^9.2.0 "@azure/arm-resources-subscriptions": ^2.0.2 - "@azure/identity": ^3.1.4 + "@azure/identity": ^3.2.2 "@azure/ms-rest-js": ^2.6.6 "@azure/ms-rest-nodeauth": ^3.1.1 "@cloud-carbon-footprint/common": ^1.10.0 From 8164d1322420c141c3f91145651334d5122611a6 Mon Sep 17 00:00:00 2001 From: Tom Kennes Date: Wed, 31 May 2023 09:54:33 +0200 Subject: [PATCH 115/251] updating lockfile --- .husky/pre-commit | 2 -- 1 file changed, 2 deletions(-) diff --git a/.husky/pre-commit b/.husky/pre-commit index 36e0313a6..383189262 100755 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -9,8 +9,6 @@ if [[ "$branch" != "changeset-release/trunk" ]]; then export TALISMAN_INTERACTIVE="false" fi - ls -alh $TALISMAN_HOME - ./scripts/copyright_check.sh && ./scripts/branch_warning.sh && $TALISMAN_HOME/talisman_hook_script pre-commit || exit 1 if [[ "$root" -gt "0" ]]; then echo 'Detecting modified files in packages directory...' From 45ee265ad67ed67ac6aca8e39c2878031a6df243 Mon Sep 17 00:00:00 2001 From: Tom Kennes Date: Wed, 31 May 2023 15:42:03 +0200 Subject: [PATCH 116/251] fixing some yarn linting issues --- .husky/pre-commit | 2 +- packages/azure/src/application/AzureCredentialsProvider.ts | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/.husky/pre-commit b/.husky/pre-commit index 383189262..f9aa0fa81 100755 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -12,6 +12,6 @@ if [[ "$branch" != "changeset-release/trunk" ]]; then ./scripts/copyright_check.sh && ./scripts/branch_warning.sh && $TALISMAN_HOME/talisman_hook_script pre-commit || exit 1 if [[ "$root" -gt "0" ]]; then echo 'Detecting modified files in packages directory...' - yarn lerna run precommit --stream + #yarn lerna run precommit --stream fi fi diff --git a/packages/azure/src/application/AzureCredentialsProvider.ts b/packages/azure/src/application/AzureCredentialsProvider.ts index 5cdbd1250..007997d1a 100644 --- a/packages/azure/src/application/AzureCredentialsProvider.ts +++ b/packages/azure/src/application/AzureCredentialsProvider.ts @@ -2,7 +2,10 @@ * © 2021 Thoughtworks, Inc. */ -import { ClientSecretCredential, WorkloadIdentityCredential } from '@azure/identity' +import { + ClientSecretCredential, + WorkloadIdentityCredential, +} from '@azure/identity' import { SecretManagerServiceClient } from '@google-cloud/secret-manager' import { configLoader } from '@cloud-carbon-footprint/common' @@ -26,7 +29,7 @@ export default class AzureCredentialsProvider { case 'workload_identity': return new WorkloadIdentityCredential({ tenantId: tenantId, - clientId: clientId + clientId: clientId, }) default: return new ClientSecretCredential(tenantId, clientId, clientSecret) From 25cfccc5174b3372e2519a22b37425fd2ef9e2df Mon Sep 17 00:00:00 2001 From: Tom Kennes Date: Wed, 31 May 2023 15:42:13 +0200 Subject: [PATCH 117/251] fixing some yarn linting issues --- .husky/pre-commit | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.husky/pre-commit b/.husky/pre-commit index f9aa0fa81..383189262 100755 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -12,6 +12,6 @@ if [[ "$branch" != "changeset-release/trunk" ]]; then ./scripts/copyright_check.sh && ./scripts/branch_warning.sh && $TALISMAN_HOME/talisman_hook_script pre-commit || exit 1 if [[ "$root" -gt "0" ]]; then echo 'Detecting modified files in packages directory...' - #yarn lerna run precommit --stream + yarn lerna run precommit --stream fi fi From 0719f22f1a94cc5689e2faf112ac1594a92ceea4 Mon Sep 17 00:00:00 2001 From: Tom Kennes Date: Fri, 2 Jun 2023 11:12:57 +0200 Subject: [PATCH 118/251] small fixes --- .talismanrc | 2 ++ docker-compose.azure.yml | 34 +++++++++++++++++++ .../azure/src/application/AzureAccount.ts | 7 ++-- .../application/AzureCredentialsProvider.ts | 4 ++- 4 files changed, 44 insertions(+), 3 deletions(-) create mode 100644 docker-compose.azure.yml diff --git a/.talismanrc b/.talismanrc index ff6cfe214..fd356158b 100644 --- a/.talismanrc +++ b/.talismanrc @@ -95,6 +95,8 @@ fileignoreconfig: checksum: 13794d10fb75b46d3c6815362fd63e41f7d98afea859a4e65bdf8c8e0efd6f0b - filename: docker-compose.yml checksum: bd3ecff05d1290b7f7d0a4ba4267c3f34037b52aa82f13c31e35ca8803103b9b +- filename: docker-compose.azure.yml + checksum: 376cb095943b9b20cbbe0b800039d46617e3444d14d013554bc00219557c3723 - filename: docker/nginx.conf checksum: a4632d785a11f199217d4229a40eba5a38180d9945d96799dc9b5efc6a3bc06e - filename: microsite/docs/ClassifyingUsageTypes.md diff --git a/docker-compose.azure.yml b/docker-compose.azure.yml new file mode 100644 index 000000000..a79908538 --- /dev/null +++ b/docker-compose.azure.yml @@ -0,0 +1,34 @@ +version: '3.9' + +services: + client: + image: cloudcarbonfootprint/client:latest + ports: + - '8001:80' + volumes: + - ./docker/nginx.conf:/etc/nginx/nginx.conf + depends_on: + - api + api: + image: docker.io/cloudcarbonfootprint/api:latest + ports: + - '4000:4000' + secrets: + - AZURE_CLIENT_ID + - AZURE_CLIENT_SECRET + - AZURE_TENANT_ID + environment: + - GCP_USE_BILLING_DATA=false + - AWS_USE_BILLING_DATA=false + - AZURE_USE_BILLING_DATA=true + - AZURE_CLIENT_ID=/run/secrets/AZURE_CLIENT_ID + - AZURE_CLIENT_SECRET=/run/secrets/AZURE_CLIENT_SECRET + - AZURE_TENANT_ID=/run/secrets/AZURE_TENANT_ID + +secrets: + AZURE_CLIENT_ID: + file: ~/.docker/secrets/AZURE_CLIENT_ID + AZURE_CLIENT_SECRET: + file: ~/.docker/secrets/AZURE_CLIENT_SECRET + AZURE_TENANT_ID: + file: ~/.docker/secrets/AZURE_TENANT_ID diff --git a/packages/azure/src/application/AzureAccount.ts b/packages/azure/src/application/AzureAccount.ts index 02e3a4aa3..b0acf51dd 100644 --- a/packages/azure/src/application/AzureAccount.ts +++ b/packages/azure/src/application/AzureAccount.ts @@ -6,7 +6,10 @@ import { SubscriptionClient, Subscription, } from '@azure/arm-resources-subscriptions' -import { ClientSecretCredential } from '@azure/identity' +import { + ClientSecretCredential, + WorkloadIdentityCredential, +} from '@azure/identity' import { ConsumptionManagementClient } from '@azure/arm-consumption' import { AdvisorManagementClient } from '@azure/arm-advisor' @@ -34,7 +37,7 @@ import AdvisorRecommendations from '../lib/AdvisorRecommendations' import { AZURE_CLOUD_CONSTANTS } from '../domain' export default class AzureAccount extends CloudProviderAccount { - private credentials: ClientSecretCredential + private credentials: ClientSecretCredential | WorkloadIdentityCredential private subscriptionClient: SubscriptionClient private logger: Logger diff --git a/packages/azure/src/application/AzureCredentialsProvider.ts b/packages/azure/src/application/AzureCredentialsProvider.ts index 007997d1a..84943d229 100644 --- a/packages/azure/src/application/AzureCredentialsProvider.ts +++ b/packages/azure/src/application/AzureCredentialsProvider.ts @@ -11,7 +11,9 @@ import { SecretManagerServiceClient } from '@google-cloud/secret-manager' import { configLoader } from '@cloud-carbon-footprint/common' export default class AzureCredentialsProvider { - static async create(): Promise { + static async create(): Promise< + ClientSecretCredential | WorkloadIdentityCredential + > { const clientId = configLoader().AZURE.authentication.clientId const clientSecret = configLoader().AZURE.authentication.clientSecret const tenantId = configLoader().AZURE.authentication.tenantId From 63b517890c9b7cb7a3f70104271ece58e70d4138 Mon Sep 17 00:00:00 2001 From: Tom Kennes Date: Fri, 2 Jun 2023 11:15:51 +0200 Subject: [PATCH 119/251] small fixes --- packages/azure/src/application/AzureCredentialsProvider.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/azure/src/application/AzureCredentialsProvider.ts b/packages/azure/src/application/AzureCredentialsProvider.ts index 84943d229..53a1613f6 100644 --- a/packages/azure/src/application/AzureCredentialsProvider.ts +++ b/packages/azure/src/application/AzureCredentialsProvider.ts @@ -28,7 +28,7 @@ export default class AzureCredentialsProvider { clientIdFromGoogle, clientSecretFromGoogle, ) - case 'workload_identity': + case 'WORKLOAD_IDENTITY': return new WorkloadIdentityCredential({ tenantId: tenantId, clientId: clientId, From dfa4dbb1b9ef41f1d9ef2f8d5de9528759729738 Mon Sep 17 00:00:00 2001 From: Tom Kennes Date: Mon, 5 Jun 2023 20:25:03 +0200 Subject: [PATCH 120/251] removing docker compose azure local --- .talismanrc | 2 -- docker-compose.azure.yml | 34 ---------------------------------- 2 files changed, 36 deletions(-) delete mode 100644 docker-compose.azure.yml diff --git a/.talismanrc b/.talismanrc index fd356158b..ff6cfe214 100644 --- a/.talismanrc +++ b/.talismanrc @@ -95,8 +95,6 @@ fileignoreconfig: checksum: 13794d10fb75b46d3c6815362fd63e41f7d98afea859a4e65bdf8c8e0efd6f0b - filename: docker-compose.yml checksum: bd3ecff05d1290b7f7d0a4ba4267c3f34037b52aa82f13c31e35ca8803103b9b -- filename: docker-compose.azure.yml - checksum: 376cb095943b9b20cbbe0b800039d46617e3444d14d013554bc00219557c3723 - filename: docker/nginx.conf checksum: a4632d785a11f199217d4229a40eba5a38180d9945d96799dc9b5efc6a3bc06e - filename: microsite/docs/ClassifyingUsageTypes.md diff --git a/docker-compose.azure.yml b/docker-compose.azure.yml deleted file mode 100644 index a79908538..000000000 --- a/docker-compose.azure.yml +++ /dev/null @@ -1,34 +0,0 @@ -version: '3.9' - -services: - client: - image: cloudcarbonfootprint/client:latest - ports: - - '8001:80' - volumes: - - ./docker/nginx.conf:/etc/nginx/nginx.conf - depends_on: - - api - api: - image: docker.io/cloudcarbonfootprint/api:latest - ports: - - '4000:4000' - secrets: - - AZURE_CLIENT_ID - - AZURE_CLIENT_SECRET - - AZURE_TENANT_ID - environment: - - GCP_USE_BILLING_DATA=false - - AWS_USE_BILLING_DATA=false - - AZURE_USE_BILLING_DATA=true - - AZURE_CLIENT_ID=/run/secrets/AZURE_CLIENT_ID - - AZURE_CLIENT_SECRET=/run/secrets/AZURE_CLIENT_SECRET - - AZURE_TENANT_ID=/run/secrets/AZURE_TENANT_ID - -secrets: - AZURE_CLIENT_ID: - file: ~/.docker/secrets/AZURE_CLIENT_ID - AZURE_CLIENT_SECRET: - file: ~/.docker/secrets/AZURE_CLIENT_SECRET - AZURE_TENANT_ID: - file: ~/.docker/secrets/AZURE_TENANT_ID From 7f5fd073ef423a337b41cc57049ed29cd33c96f4 Mon Sep 17 00:00:00 2001 From: Cam Casher Date: Mon, 5 Jun 2023 15:58:51 -0600 Subject: [PATCH 121/251] adds advisor documentation --- microsite/docs/ConnectingData/Recommendations.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/microsite/docs/ConnectingData/Recommendations.md b/microsite/docs/ConnectingData/Recommendations.md index a43aaf07e..e87ae4372 100644 --- a/microsite/docs/ConnectingData/Recommendations.md +++ b/microsite/docs/ConnectingData/Recommendations.md @@ -114,6 +114,20 @@ Required permissions necessary for the GCP Recommender Client: | resourcemanager.projects.list | +## Azure +### Advisor +Azure provides a service named Advisor that helps optimize Azure deployments. It analyzes your usage and resource configuration and then recommends solutions that can help you improve the cost effectiveness, performance, reliability, and security of Azure resources. +Currently, Cloud Carbon Footprint uses the Advisor API to retrieve recommendations for Azure Virtual Machines by filtering for the Cost category. + +For further information regarding the Azure feature, you can view [this documentation](https://docs.microsoft.com/en-us/azure/advisor/advisor-cost-recommendations) + +The types of recommendations we provide regarding Azure Virtual Machines are: +- Shutdown idle virtual machines +- Resize virtual machines for optimization (now called "Sku change") + +#### Permissions +In order to retrieve Azure Advisor recommendations, access for Advisor must be enabled. [Here](https://docs.microsoft.com/en-us/azure/advisor/advisor-cost-recommendations#enable-advisor) is documentation for enabling access. + ## Query Parameter Options In the case of AWS Right-sizing recommendations, we have provided an optional query parameter `awsRecommendationTarget` to customize modify type recommendations that are returned. From c82bf5fd2f2768b8be42d64f41dcc280ffc9eb73 Mon Sep 17 00:00:00 2001 From: Arik Smith Date: Tue, 6 Jun 2023 14:45:10 -0400 Subject: [PATCH 122/251] [1022] adds support for chunking azure requests by subscriptions --- .changeset/angry-seals-camp.md | 5 + .changeset/funny-candles-hide.md | 6 + packages/api/.env.template | 5 +- .../azure/src/__tests__/AzureAccount.test.ts | 254 ++++++++++++------ .../azure/src/application/AzureAccount.ts | 89 ++++-- packages/cli/.env.template | 3 + packages/common/src/Config.ts | 4 + 7 files changed, 255 insertions(+), 111 deletions(-) create mode 100644 .changeset/angry-seals-camp.md create mode 100644 .changeset/funny-candles-hide.md diff --git a/.changeset/angry-seals-camp.md b/.changeset/angry-seals-camp.md new file mode 100644 index 000000000..3f0d99fe8 --- /dev/null +++ b/.changeset/angry-seals-camp.md @@ -0,0 +1,5 @@ +--- +'@cloud-carbon-footprint/azure': minor +--- + +Adds config support for chunking/splitting azure requests by subscription diff --git a/.changeset/funny-candles-hide.md b/.changeset/funny-candles-hide.md new file mode 100644 index 000000000..b44a666bd --- /dev/null +++ b/.changeset/funny-candles-hide.md @@ -0,0 +1,6 @@ +--- +'@cloud-carbon-footprint/cli': minor +'@cloud-carbon-footprint/common': minor +--- + +Adds config support for chunking/splitting azure requests by subscription diff --git a/packages/api/.env.template b/packages/api/.env.template index 2f2ce99e6..ddf22114d 100644 --- a/packages/api/.env.template +++ b/packages/api/.env.template @@ -63,9 +63,12 @@ AZURE_AUTH_MODE=default # Azure resource tag names to include if present, include resourceGroup as a tag name if needed: AZURE_RESOURCE_TAG_NAMES=["resourceGroup"] # eg. ["resourceGroup","project","customer"] -# To avoid rate limiting, azure estimations can be chunked by days +# To avoid rate limiting, azure estimations can be chunked by days (takes precedence over AZURE_SUBSCRIPTION_CHUNKS) AZURE_CONSUMPTION_CHUNKS_DAYS=0 +# To avoid rate limiting, asynchronous consumption management calls can be chunked by subscription +AZURE_SUBSCRIPTION_CHUNKS=0 + # Cache # Optionally set which cache you are using (defaults to local) diff --git a/packages/azure/src/__tests__/AzureAccount.test.ts b/packages/azure/src/__tests__/AzureAccount.test.ts index 94bf4b08b..1e7e5eaf4 100644 --- a/packages/azure/src/__tests__/AzureAccount.test.ts +++ b/packages/azure/src/__tests__/AzureAccount.test.ts @@ -7,6 +7,7 @@ import { LookupTableInput, LookupTableOutput, RecommendationResult, + configLoader, } from '@cloud-carbon-footprint/common' import AzureAccount from '../application/AzureAccount' @@ -47,101 +48,180 @@ describe('Azure Account', () => { const testAccountId = 'test-subscription-id' const testAccountName = 'test-subscription' - it('gets results from getDataFromConsumptionManagement function', async () => { - const mockCredentials = { - clientId: 'test-client-id', - secret: 'test-client-secret', - domain: 'test-tenant-id', - } - ;(createCredentialsSpy as jest.Mock).mockResolvedValue(mockCredentials) + describe('getDataFromConsumptionManagement', () => { + beforeEach(() => { + jest.clearAllMocks() + }) - mockListSubscriptions.list.mockReturnValue([ - { subscriptionId: 'sub-1' }, - { subscriptionId: 'sub-2' }, - ]) + it('gets results from getDataFromConsumptionManagement function', async () => { + const mockCredentials = { + clientId: 'test-client-id', + secret: 'test-client-secret', + domain: 'test-tenant-id', + } + ;(createCredentialsSpy as jest.Mock).mockResolvedValue(mockCredentials) - const mockEstimates: EstimationResult[] = [ - { - timestamp: startDate, - serviceEstimates: [ - { - kilowattHours: 0.09313874999999999, - co2e: 0.000021235635, - usesAverageCPUConstant: true, - cloudProvider: 'AZURE', - accountId: testAccountId, - accountName: testAccountName, - serviceName: 'Virtual Machines', - cost: 5, - region: 'UK South', - }, - ], - periodStartDate: startDate, - periodEndDate: endDate, - groupBy: grouping, - }, - ] + mockListSubscriptions.list.mockReturnValue([ + { subscriptionId: 'sub-1' }, + { subscriptionId: 'sub-2' }, + ]) - ;(getEstimatesSpy as jest.Mock).mockResolvedValue(mockEstimates) + const mockEstimates: EstimationResult[] = [ + { + timestamp: startDate, + serviceEstimates: [ + { + kilowattHours: 0.09313874999999999, + co2e: 0.000021235635, + usesAverageCPUConstant: true, + cloudProvider: 'AZURE', + accountId: testAccountId, + accountName: testAccountName, + serviceName: 'Virtual Machines', + cost: 5, + region: 'UK South', + }, + ], + periodStartDate: startDate, + periodEndDate: endDate, + groupBy: grouping, + }, + ] - // when - const azureAccount = new AzureAccount() - await azureAccount.initializeAccount() - const results = await azureAccount.getDataFromConsumptionManagement( - startDate, - endDate, - grouping, - ) + ;(getEstimatesSpy as jest.Mock).mockResolvedValue(mockEstimates) - // then - const expectedEstimates: EstimationResult[] = [ - { - timestamp: startDate, - serviceEstimates: [ - { - kilowattHours: 0.09313874999999999, - co2e: 0.000021235635, - usesAverageCPUConstant: true, - cloudProvider: 'AZURE', - accountId: testAccountId, - accountName: testAccountName, - serviceName: 'Virtual Machines', - cost: 5, - region: 'UK South', - }, - ], - periodStartDate: startDate, - periodEndDate: endDate, - groupBy: grouping, - }, - { - timestamp: startDate, - serviceEstimates: [ + const promiseSpy = jest.spyOn(Promise, 'all') + + // when + const azureAccount = new AzureAccount() + await azureAccount.initializeAccount() + const results = await azureAccount.getDataFromConsumptionManagement( + startDate, + endDate, + grouping, + ) + + // then + const expectedEstimates: EstimationResult[] = [ + { + timestamp: startDate, + serviceEstimates: [ + { + kilowattHours: 0.09313874999999999, + co2e: 0.000021235635, + usesAverageCPUConstant: true, + cloudProvider: 'AZURE', + accountId: testAccountId, + accountName: testAccountName, + serviceName: 'Virtual Machines', + cost: 5, + region: 'UK South', + }, + ], + periodStartDate: startDate, + periodEndDate: endDate, + groupBy: grouping, + }, + { + timestamp: startDate, + serviceEstimates: [ + { + kilowattHours: 0.09313874999999999, + co2e: 0.000021235635, + usesAverageCPUConstant: true, + cloudProvider: 'AZURE', + accountId: testAccountId, + accountName: testAccountName, + serviceName: 'Virtual Machines', + cost: 5, + region: 'UK South', + }, + ], + periodStartDate: startDate, + periodEndDate: endDate, + groupBy: grouping, + }, + ] + + expect(results).toEqual(expectedEstimates) + expect(getEstimatesSpy).toHaveBeenNthCalledWith( + 2, + startDate, + endDate, + grouping, + ) + expect(promiseSpy).toBeCalledTimes(1) + }) + + describe('Fetch Configurations', () => { + it('fetches data for all subscriptions when subscription chunks are set', async () => { + const mockCredentials = { + clientId: 'test-client-id', + secret: 'test-client-secret', + domain: 'test-tenant-id', + } + ;(createCredentialsSpy as jest.Mock).mockResolvedValue(mockCredentials) + mockListSubscriptions.list.mockReturnValue([ + { subscriptionId: 'sub-1' }, + { subscriptionId: 'sub-2' }, + { subscriptionId: 'sub-3' }, + { subscriptionId: 'sub-4' }, + { subscriptionId: 'sub-5' }, + ]) + + const mockEstimates: EstimationResult[] = [ { - kilowattHours: 0.09313874999999999, - co2e: 0.000021235635, - usesAverageCPUConstant: true, - cloudProvider: 'AZURE', - accountId: testAccountId, - accountName: testAccountName, - serviceName: 'Virtual Machines', - cost: 5, - region: 'UK South', + timestamp: startDate, + serviceEstimates: [ + { + kilowattHours: 0.09313874999999999, + co2e: 0.000021235635, + usesAverageCPUConstant: true, + cloudProvider: 'AZURE', + accountId: testAccountId, + accountName: testAccountName, + serviceName: 'Virtual Machines', + cost: 5, + region: 'UK South', + }, + ], + periodStartDate: startDate, + periodEndDate: endDate, + groupBy: grouping, }, - ], - periodStartDate: startDate, - periodEndDate: endDate, - groupBy: grouping, - }, - ] + ] - expect(results).toEqual(expectedEstimates) - expect(getEstimatesSpy).toHaveBeenNthCalledWith( - 2, - startDate, - endDate, - grouping, - ) + ;(getEstimatesSpy as jest.Mock).mockResolvedValue(mockEstimates) + + const AZURE = configLoader().AZURE || {} + AZURE.SUBSCRIPTION_CHUNKS = 2 + const promiseSpy = jest.spyOn(Promise, 'all') + // when + const azureAccount = new AzureAccount() + await azureAccount.initializeAccount() + const results = await azureAccount.getDataFromConsumptionManagement( + startDate, + endDate, + grouping, + ) + + // Results should be the same as the mockEstimates array repeated 5 times + const expectedEstimates = Array.from( + { length: 5 }, + () => mockEstimates[0], + ) + + expect(results).toEqual(expectedEstimates) + + // getEstimates should have been called 5 times (once for each subscription) + expect(getEstimatesSpy).toHaveBeenCalledTimes(5) + // Promise.all should have been called 3 times (once for each chunk of 2 subscriptions) + expect(promiseSpy).toHaveBeenCalledTimes(3) + + // Reset the config + AZURE.SUBSCRIPTION_CHUNKS = 0 + }) + }) }) it('gets results from getDataFromAdvisorManagement function', async () => { diff --git a/packages/azure/src/application/AzureAccount.ts b/packages/azure/src/application/AzureAccount.ts index b0acf51dd..77885942d 100644 --- a/packages/azure/src/application/AzureAccount.ts +++ b/packages/azure/src/application/AzureAccount.ts @@ -30,8 +30,8 @@ import { LookupTableInput, RecommendationResult, } from '@cloud-carbon-footprint/common' +import R from 'ramda' import AzureCredentialsProvider from './AzureCredentialsProvider' - import ConsumptionManagementService from '../lib/ConsumptionManagement' import AdvisorRecommendations from '../lib/AdvisorRecommendations' import { AZURE_CLOUD_CONSTANTS } from '../domain' @@ -84,36 +84,43 @@ export default class AzureAccount extends CloudProviderAccount { const AZURE = configLoader().AZURE - const requests = subscriptions.map((subscription) => { - return async () => { - try { - this.logger.info(`Getting data for ${subscription.displayName}...`) - return await this.getDataForSubscription( - startDate, - endDate, - subscription.subscriptionId, - grouping, - ) - } catch (e) { - this.logger.warn( - `Unable to get estimate data for Azure subscription ${subscription.subscriptionId}: ${e.message}`, - ) - return [] - } - } - }) + const requests = this.createSubscriptionRequests( + subscriptions, + startDate, + endDate, + grouping, + ) + // If chunking by day is enabled, synchronously fetch each subscription if (AZURE.CONSUMPTION_CHUNKS_DAYS) { const estimationResults: Array> = [] for (const request of requests) { estimationResults.push(await request()) } return estimationResults.flat() - } else { - return ( - await Promise.all(requests.map(async (request) => request())) - ).flat() } + + // If chunking by day is disabled, asynchronously fetch all or chunked subscriptions + const chunkedRequests = AZURE.SUBSCRIPTION_CHUNKS + ? R.splitEvery(AZURE.SUBSCRIPTION_CHUNKS, requests) + : [requests] + this.logger.debug( + `Fetching Azure consumption data with ${AZURE.SUBSCRIPTION_CHUNKS}} 1} chunk(s)`, + ) + + // TODO: Remove before release. + console.time(`Azure Subscriptions: ${AZURE.SUBSCRIPTION_CHUNKS} chunk(s)`) + const estimationResults = [] + for (const requests of chunkedRequests) { + estimationResults.push( + await Promise.all(requests.map(async (request) => request())), + ) + } + console.timeEnd( + `Azure Subscriptions: ${AZURE.SUBSCRIPTION_CHUNKS || 1} chunk(s)`, + ) + + return R.flatten(estimationResults) } public async getSubscriptions(): Promise { @@ -182,4 +189,40 @@ export default class AzureAccount extends CloudProviderAccount { grouping, ) } + + /** + * Creates an array of functions that each return a promise for EstimationResults. + * Each Promise corresponds to a mapped getDataForSubscription result for that subscription. + * + * @param {Subscription[]} subscriptions - An array of subscription information to retrieve data for. + * @param {Date} startDate - The start date for the estimation request period. + * @param {Date} endDate - The end date for the estimation request period. + * @param {GroupBy} grouping - The grouping method used intended for the estimation request. + * @returns {(() => Promise)[]} An array of functions that each return a promise for an array of estimation results. + */ + private createSubscriptionRequests( + subscriptions: Subscription[], + startDate: Date, + endDate: Date, + grouping: GroupBy, + ): (() => Promise)[] { + return subscriptions.map((subscription) => { + return async () => { + try { + this.logger.info(`Getting data for ${subscription.displayName}...`) + return await this.getDataForSubscription( + startDate, + endDate, + subscription.subscriptionId, + grouping, + ) + } catch (e) { + this.logger.warn( + `Unable to get estimate data for Azure subscription ${subscription.subscriptionId}: ${e.message}`, + ) + return [] + } + } + }) + } } diff --git a/packages/cli/.env.template b/packages/cli/.env.template index 674069e39..b5161ea6c 100644 --- a/packages/cli/.env.template +++ b/packages/cli/.env.template @@ -66,6 +66,9 @@ AZURE_RESOURCE_TAG_NAMES=["resourceGroup"] # eg. ["resourceGroup","project","cus # To avoid rate limiting, azure estimations can be chunked by days AZURE_CONSUMPTION_CHUNKS_DAYS=0 +# To avoid rate limiting, asynchronous consumption management calls can be chunked by subscription (takes precedence over AZURE_SUBSCRIPTION_CHUNKS) +AZURE_SUBSCRIPTION_CHUNKS=0 + # Cache # Optionally set which cache you are using (defaults to local) diff --git a/packages/common/src/Config.ts b/packages/common/src/Config.ts index 30f87febd..b5c004be5 100644 --- a/packages/common/src/Config.ts +++ b/packages/common/src/Config.ts @@ -59,6 +59,7 @@ export interface CCFConfig { } RESOURCE_TAG_NAMES?: string[] CONSUMPTION_CHUNKS_DAYS?: number + SUBSCRIPTION_CHUNKS?: number } LOGGING_MODE?: string CACHE_MODE?: string @@ -263,6 +264,9 @@ const getConfig = (): CCFConfig => ({ CONSUMPTION_CHUNKS_DAYS: parseInt( getEnvVar('AZURE_CONSUMPTION_CHUNKS_DAYS') || '0', ), + SUBSCRIPTION_CHUNKS: parseInt( + getEnvVar('AZURE_SUBSCRIPTION_CHUNKS') || '0', + ), }, LOGGING_MODE: process.env.LOGGING_MODE || '', CACHE_MODE: getEnvVar('CACHE_MODE') || '', From 946b68f8613c97fbf3802d607a0d44f1ecfc583c Mon Sep 17 00:00:00 2001 From: Arik Smith Date: Tue, 6 Jun 2023 15:09:10 -0400 Subject: [PATCH 123/251] update env templates --- packages/api/.env.template | 3 +++ packages/cli/.env.template | 3 +++ .../templates/default-app/packages/cli/.env.template | 9 +++++++++ 3 files changed, 15 insertions(+) diff --git a/packages/api/.env.template b/packages/api/.env.template index ddf22114d..b73b3fd3e 100644 --- a/packages/api/.env.template +++ b/packages/api/.env.template @@ -47,6 +47,8 @@ GCP_VCPUS_PER_CLOUD_COMPOSER_ENVIRONMENT=10 # Variables needed for the Cloud Usage API (Higher Accuracy) approach with GCP: GCP_PROJECTS=[{"id":"your-gcp-project-id","name":"Your GCP Project"}] +GCP_RESOURCE_TAG_NAMES=[] # ["tag:ise-api-enabler-access, label:goog-composer-location, project:twproject"] + # Azure # Variables needed for the Billing Data (Holistic) approach with Azure: @@ -73,6 +75,7 @@ AZURE_SUBSCRIPTION_CHUNKS=0 # Optionally set which cache you are using (defaults to local) CACHE_MODE=MONGODB + # If CACHE_MODE is set to 'MONGODB', you need to provide the URI to your db and a file path to your credentials MONGODB_URI=mongodb://localhost:27017 MONGODB_CREDENTIALS=/keys/mongodb-certificate.pem diff --git a/packages/cli/.env.template b/packages/cli/.env.template index b5161ea6c..0e006f086 100644 --- a/packages/cli/.env.template +++ b/packages/cli/.env.template @@ -47,6 +47,9 @@ GCP_VCPUS_PER_CLOUD_COMPOSER_ENVIRONMENT=10 # Variables needed for the Cloud Usage API (Higher Accuracy) approach with GCP: GCP_PROJECTS=[{"id":"your-gcp-project-id","name":"Your GCP Project"}] +# Google Cloud resource tag or label names to include if present +GCP_RESOURCE_TAG_NAMES=[] # ["tag:ise-api-enabler-access, label:goog-composer-location, project:twproject"] + # Azure # Variables needed for the Billing Data (Holistic) approach with Azure: diff --git a/packages/create-app/templates/default-app/packages/cli/.env.template b/packages/create-app/templates/default-app/packages/cli/.env.template index 35a37d202..b5161ea6c 100644 --- a/packages/create-app/templates/default-app/packages/cli/.env.template +++ b/packages/create-app/templates/default-app/packages/cli/.env.template @@ -60,6 +60,15 @@ AZURE_TENANT_ID=your-azure-tenant-id # Optionally set this to "GCP" if your Azure credentials are stored in Google Secrets Manager. AZURE_AUTH_MODE=default +# Azure resource tag names to include if present, include resourceGroup as a tag name if needed: +AZURE_RESOURCE_TAG_NAMES=["resourceGroup"] # eg. ["resourceGroup","project","customer"] + +# To avoid rate limiting, azure estimations can be chunked by days +AZURE_CONSUMPTION_CHUNKS_DAYS=0 + +# To avoid rate limiting, asynchronous consumption management calls can be chunked by subscription (takes precedence over AZURE_SUBSCRIPTION_CHUNKS) +AZURE_SUBSCRIPTION_CHUNKS=0 + # Cache # Optionally set which cache you are using (defaults to local) From 7688b08620ecde8d91001b79c79c00ecc34b2d04 Mon Sep 17 00:00:00 2001 From: Arik Smith Date: Tue, 6 Jun 2023 15:11:08 -0400 Subject: [PATCH 124/251] changeset: Updates env templates with new configuration options --- .changeset/moody-phones-teach.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/moody-phones-teach.md diff --git a/.changeset/moody-phones-teach.md b/.changeset/moody-phones-teach.md new file mode 100644 index 000000000..8b44b5b76 --- /dev/null +++ b/.changeset/moody-phones-teach.md @@ -0,0 +1,5 @@ +--- +'@cloud-carbon-footprint/create-app': patch +--- + +Updates env templates with new configuration options From e5ff25e7cc70ea6d154f063c4567b46fa9b8ea59 Mon Sep 17 00:00:00 2001 From: Arik Smith Date: Thu, 8 Jun 2023 21:11:32 -0400 Subject: [PATCH 125/251] add mongo static connector and remove per-request connections --- packages/app/src/MongoDbCacheManager.ts | 59 ++++++------ .../src/__tests__/MongoDbCacheManager.test.ts | 92 ++++++++++--------- packages/app/src/index.ts | 3 + 3 files changed, 81 insertions(+), 73 deletions(-) diff --git a/packages/app/src/MongoDbCacheManager.ts b/packages/app/src/MongoDbCacheManager.ts index 8b5e2c525..8f2386a60 100644 --- a/packages/app/src/MongoDbCacheManager.ts +++ b/packages/app/src/MongoDbCacheManager.ts @@ -4,37 +4,56 @@ import moment, { Moment } from 'moment' import { MongoClient, ServerApiVersion } from 'mongodb' -import { configLoader, EstimationResult } from '@cloud-carbon-footprint/common' +import { + configLoader, + EstimationResult, + Logger, +} from '@cloud-carbon-footprint/common' import CacheManager from './CacheManager' import { EstimationRequest } from './CreateValidRequest' import { getDatesWithinRequestTimeFrame } from './common/helpers' export default class MongoDbCacheManager extends CacheManager { - mongoClient: MongoClient + static mongoClient: MongoClient mongoDbName: string + constructor() { super() this.mongoDbName = 'ccf' } - async createDbConnection() { + /** + * Creates a connection to the MongoDB client using the provided URI and any credentials. + * If no credentials are provided, the connection will be made without SSL. + * This function needs to be invoked before using this MongoDbCacheManager and making any requests to the MongoDB client. + * @returns {Promise} A Promise that resolves when the connection is established. + */ + static async createDbConnection() { + const logger = new Logger('MongoDB') const mongoURI = configLoader().MONGODB.URI const mongoCredentials = configLoader().MONGODB.CREDENTIALS + if (mongoCredentials && mongoURI) { - this.mongoClient = new MongoClient(mongoURI, { + MongoDbCacheManager.mongoClient = new MongoClient(mongoURI, { sslKey: mongoCredentials, sslCert: mongoCredentials, serverApi: ServerApiVersion.v1, }) - this.cacheLogger.debug('Successfully connected to the mongoDB client') } else if (mongoURI) { - this.mongoClient = new MongoClient(mongoURI) - this.cacheLogger.debug('Successfully connected to the mongoDB client') + MongoDbCacheManager.mongoClient = new MongoClient(mongoURI) } else { - this.cacheLogger.warn( - `There was an error connecting to the MongoDB client, please provide a valid URI`, + logger.error( + 'There was an error connecting to the MongoDB client', + new Error('Please provide a valid URI'), ) } + + try { + await MongoDbCacheManager.mongoClient.connect() + logger.info('Successfully connected to the MongoDB client') + } catch (error) { + logger.error('There was an error connecting to the MongoDB client', error) + } } async loadEstimates( @@ -82,13 +101,9 @@ export default class MongoDbCacheManager extends CacheManager { ): Promise { let savedEstimates: EstimationResult[] = [] try { - await this.createDbConnection() - // Connect the client to the server - await this.mongoClient.connect() - // Specify a database to query const collectionName = `estimates-by-${grouping}` - const database = this.mongoClient.db(this.mongoDbName) + const database = MongoDbCacheManager.mongoClient.db(this.mongoDbName) const estimates = await this.loadEstimates( database, @@ -101,9 +116,6 @@ export default class MongoDbCacheManager extends CacheManager { this.cacheLogger.warn( `There was an error getting estimates from MongoDB: ${e.message}`, ) - } finally { - // Ensures that the client will close when you finish/error - await this.mongoClient.close() } return savedEstimates } @@ -113,11 +125,8 @@ export default class MongoDbCacheManager extends CacheManager { grouping: string, ): Promise { try { - await this.createDbConnection() - await this.mongoClient.connect() - const collectionName = `estimates-by-${grouping}` - const database = this.mongoClient.db(this.mongoDbName) + const database = MongoDbCacheManager.mongoClient.db(this.mongoDbName) const collection = database.collection(collectionName) const newEstimates: any[] = [] @@ -137,9 +146,6 @@ export default class MongoDbCacheManager extends CacheManager { this.cacheLogger.warn( `There was an error setting estimates from MongoDB: ${e.message}`, ) - } finally { - // Ensures that the client will close when you finish/error - await this.mongoClient.close() } } @@ -155,11 +161,8 @@ export default class MongoDbCacheManager extends CacheManager { } try { - await this.createDbConnection() - await this.mongoClient.connect() - const collectionName = `estimates-by-${grouping}` - const database = this.mongoClient.db(this.mongoDbName) + const database = MongoDbCacheManager.mongoClient.db(this.mongoDbName) const aggResult: any = await new Promise((resolve, reject) => { database diff --git a/packages/app/src/__tests__/MongoDbCacheManager.test.ts b/packages/app/src/__tests__/MongoDbCacheManager.test.ts index 00d132605..b8a01684d 100644 --- a/packages/app/src/__tests__/MongoDbCacheManager.test.ts +++ b/packages/app/src/__tests__/MongoDbCacheManager.test.ts @@ -17,7 +17,11 @@ import { EstimationRequest } from '../CreateValidRequest' jest.mock('mongodb', () => { return { MongoClient: jest.fn().mockImplementation(() => { - return {} + return { + connect: jest.fn(), + close: jest.fn(), + db: jest.fn().mockReturnValue({}), + } }), ServerApiVersion: jest.fn().mockImplementation(() => { return {} @@ -52,18 +56,17 @@ const mockAggregation = jest.fn() describe('MongoDbCacheManager', () => { afterEach(() => { - jest.resetAllMocks() + jest.clearAllMocks() }) it('connects to the mongodb server with URI', async () => { - jest.spyOn(Logger.prototype, 'debug').mockImplementation() + jest.spyOn(Logger.prototype, 'info').mockImplementation() - const mongoDbCacheManager = new MongoDbCacheManager() - await mongoDbCacheManager.createDbConnection() + await MongoDbCacheManager.createDbConnection() expect(mongo.MongoClient).toHaveBeenCalledWith('test-mongo-uri') - expect(Logger.prototype.debug).toHaveBeenCalledWith( - 'Successfully connected to the mongoDB client', + expect(Logger.prototype.info).toHaveBeenCalledWith( + 'Successfully connected to the MongoDB client', ) }) @@ -75,10 +78,9 @@ describe('MongoDbCacheManager', () => { CREDENTIALS: 'test-mongo-credentials', }, }) - jest.spyOn(Logger.prototype, 'debug').mockImplementation() + jest.spyOn(Logger.prototype, 'info').mockImplementation() - const mongoDbCacheManager = new MongoDbCacheManager() - await mongoDbCacheManager.createDbConnection() + await MongoDbCacheManager.createDbConnection() expect(mongo.MongoClient).toHaveBeenCalledWith('test-mongo-uri', { serverApi: undefined, @@ -86,8 +88,8 @@ describe('MongoDbCacheManager', () => { sslKey: 'test-mongo-credentials', }) - expect(Logger.prototype.debug).toHaveBeenCalledWith( - 'Successfully connected to the mongoDB client', + expect(Logger.prototype.info).toHaveBeenCalledWith( + 'Successfully connected to the MongoDB client', ) }) @@ -99,14 +101,14 @@ describe('MongoDbCacheManager', () => { }, }) - jest.spyOn(Logger.prototype, 'warn').mockImplementation() + jest.spyOn(Logger.prototype, 'error').mockImplementation() - const mongoDbCacheManager = new MongoDbCacheManager() - await mongoDbCacheManager.createDbConnection() + await MongoDbCacheManager.createDbConnection() expect(mongo.MongoClient).not.toHaveBeenCalled() - expect(Logger.prototype.warn).toHaveBeenCalledWith( - 'There was an error connecting to the MongoDB client, please provide a valid URI', + expect(Logger.prototype.error).toHaveBeenCalledWith( + 'There was an error connecting to the MongoDB client', + new Error('Please provide a valid URI'), ) }) @@ -149,9 +151,9 @@ describe('MongoDbCacheManager', () => { const mongoDbCacheManager = new MongoDbCacheManager() jest - .spyOn(MongoDbCacheManager.prototype, 'createDbConnection') + .spyOn(MongoDbCacheManager, 'createDbConnection') .mockImplementation(async () => { - mongoDbCacheManager.mongoClient = mockClient as mongo.MongoClient + MongoDbCacheManager.mongoClient = mockClient as mongo.MongoClient }) const loadEstimatesSpy = jest.spyOn(mongoDbCacheManager, 'loadEstimates') @@ -167,9 +169,9 @@ describe('MongoDbCacheManager', () => { const mongoDbCacheManager = new MongoDbCacheManager() jest - .spyOn(MongoDbCacheManager.prototype, 'createDbConnection') + .spyOn(MongoDbCacheManager, 'createDbConnection') .mockImplementation(async () => { - mongoDbCacheManager.mongoClient = mockClient as mongo.MongoClient + MongoDbCacheManager.mongoClient = mockClient as mongo.MongoClient }) const testErrorMessage = 'Example error' @@ -239,14 +241,14 @@ describe('MongoDbCacheManager', () => { const mongoDbCacheManager = new MongoDbCacheManager() jest - .spyOn(MongoDbCacheManager.prototype, 'createDbConnection') + .spyOn(MongoDbCacheManager, 'createDbConnection') .mockImplementation(async () => { - mongoDbCacheManager.mongoClient = mockClient as mongo.MongoClient + MongoDbCacheManager.mongoClient = mockClient as mongo.MongoClient }) - await mongoDbCacheManager.createDbConnection() + await MongoDbCacheManager.createDbConnection() const estimates = await mongoDbCacheManager.loadEstimates( - mongoDbCacheManager.mongoClient.db(mongoDbCacheManager.mongoDbName), + MongoDbCacheManager.mongoClient.db(mongoDbCacheManager.mongoDbName), 'estimates-by-day', request, ) @@ -281,15 +283,15 @@ describe('MongoDbCacheManager', () => { const mongoDbCacheManager = new MongoDbCacheManager() jest.spyOn(Logger.prototype, 'info').mockImplementation() jest - .spyOn(MongoDbCacheManager.prototype, 'createDbConnection') + .spyOn(MongoDbCacheManager, 'createDbConnection') .mockImplementation(async () => { - mongoDbCacheManager.mongoClient = + MongoDbCacheManager.mongoClient = mockClientWithoutCollection as mongo.MongoClient }) - await mongoDbCacheManager.createDbConnection() + await MongoDbCacheManager.createDbConnection() const estimates = await mongoDbCacheManager.loadEstimates( - mongoDbCacheManager.mongoClient.db(mongoDbCacheManager.mongoDbName), + MongoDbCacheManager.mongoClient.db(mongoDbCacheManager.mongoDbName), 'estimates-by-day', request, ) @@ -315,14 +317,14 @@ describe('MongoDbCacheManager', () => { const mongoDbCacheManager = new MongoDbCacheManager() jest - .spyOn(MongoDbCacheManager.prototype, 'createDbConnection') + .spyOn(MongoDbCacheManager, 'createDbConnection') .mockImplementation(async () => { - mongoDbCacheManager.mongoClient = mockClient as mongo.MongoClient + MongoDbCacheManager.mongoClient = mockClient as mongo.MongoClient }) - await mongoDbCacheManager.createDbConnection() + await MongoDbCacheManager.createDbConnection() const estimates = await mongoDbCacheManager.loadEstimates( - mongoDbCacheManager.mongoClient.db(mongoDbCacheManager.mongoDbName), + MongoDbCacheManager.mongoClient.db(mongoDbCacheManager.mongoDbName), 'estimates-by-day', request, ) @@ -453,11 +455,12 @@ describe('MongoDbCacheManager', () => { } jest - .spyOn(MongoDbCacheManager.prototype, 'createDbConnection') + .spyOn(MongoDbCacheManager, 'createDbConnection') .mockImplementation(async () => { - mongoDbCacheManager.mongoClient = mockClient as mongo.MongoClient + MongoDbCacheManager.mongoClient = mockClient as mongo.MongoClient }) + await MongoDbCacheManager.createDbConnection() await mongoDbCacheManager.setEstimates(mockEstimates, 'day') // Should insert flat service estimates with timestamp and groupBy appended to each @@ -505,7 +508,6 @@ describe('MongoDbCacheManager', () => { expect(mockCollection).toHaveBeenCalledWith('estimates-by-day') expect(mockInsertMany).toHaveBeenCalledWith(expectedInsertedEstimates) - expect(mockClient.close).toHaveBeenCalled() }) }) describe('gets missing dates', () => { @@ -553,9 +555,9 @@ describe('MongoDbCacheManager', () => { const mongoDbCacheManager = new MongoDbCacheManager() jest - .spyOn(MongoDbCacheManager.prototype, 'createDbConnection') + .spyOn(MongoDbCacheManager, 'createDbConnection') .mockImplementation(async () => { - mongoDbCacheManager.mongoClient = mockClient as mongo.MongoClient + MongoDbCacheManager.mongoClient = mockClient as mongo.MongoClient }) mockGetDates.mockReturnValue(mockMissingDates) @@ -575,7 +577,7 @@ describe('MongoDbCacheManager', () => { }, ] - await mongoDbCacheManager.createDbConnection() + await MongoDbCacheManager.createDbConnection() const missingDates = await mongoDbCacheManager.getMissingDates( request, 'day', @@ -613,9 +615,9 @@ describe('MongoDbCacheManager', () => { const mongoDbCacheManager = new MongoDbCacheManager() jest - .spyOn(MongoDbCacheManager.prototype, 'createDbConnection') + .spyOn(MongoDbCacheManager, 'createDbConnection') .mockImplementation(async () => { - mongoDbCacheManager.mongoClient = mockClient as mongo.MongoClient + MongoDbCacheManager.mongoClient = mockClient as mongo.MongoClient }) mockGetDates.mockReturnValue(mockMissingDates) @@ -642,7 +644,7 @@ describe('MongoDbCacheManager', () => { }, ] - await mongoDbCacheManager.createDbConnection() + await MongoDbCacheManager.createDbConnection() const missingDates = await mongoDbCacheManager.getMissingDates( newRequest, 'day', @@ -665,12 +667,12 @@ describe('MongoDbCacheManager', () => { jest.spyOn(Logger.prototype, 'warn').mockImplementation() jest - .spyOn(MongoDbCacheManager.prototype, 'createDbConnection') + .spyOn(MongoDbCacheManager, 'createDbConnection') .mockImplementation(async () => { - mongoDbCacheManager.mongoClient = mockClient as mongo.MongoClient + MongoDbCacheManager.mongoClient = mockClient as mongo.MongoClient }) - await mongoDbCacheManager.createDbConnection() + await MongoDbCacheManager.createDbConnection() const missingDates = await mongoDbCacheManager.getMissingDates( request, 'day', diff --git a/packages/app/src/index.ts b/packages/app/src/index.ts index 19ebf2009..b9c849401 100644 --- a/packages/app/src/index.ts +++ b/packages/app/src/index.ts @@ -12,3 +12,6 @@ export { RecommendationsRawRequest, Tags, } from './RawRequest' +export { default as MongoDbCacheManager } from './MongoDbCacheManager' +export { default as LocalCacheManager } from './LocalCacheManager' +export { default as GoogleCloudCacheManager } from './GoogleCloudCacheManager' From 7858f2b5a7ca625560ae31dd7124420d14ccde2e Mon Sep 17 00:00:00 2001 From: Arik Smith Date: Thu, 8 Jun 2023 21:16:19 -0400 Subject: [PATCH 126/251] add support for opening and closing enabled mongodb connections to the server --- packages/api/src/api.ts | 2 +- packages/api/src/server.ts | 20 ++++++++++++++++++-- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/packages/api/src/api.ts b/packages/api/src/api.ts index faaf1313e..b39bc3184 100644 --- a/packages/api/src/api.ts +++ b/packages/api/src/api.ts @@ -22,7 +22,7 @@ import { RecommendationsRequestValidationError, } from '@cloud-carbon-footprint/common' -const apiLogger = new Logger('api') +const apiLogger = new Logger('API') /** * Returns the raw estimates diff --git a/packages/api/src/server.ts b/packages/api/src/server.ts index 8559303f8..617f073f3 100644 --- a/packages/api/src/server.ts +++ b/packages/api/src/server.ts @@ -12,11 +12,12 @@ import cors, { CorsOptions } from 'cors' import { createRouter } from './api' import auth from './auth' -import { Logger } from '@cloud-carbon-footprint/common' +import { Logger, configLoader } from '@cloud-carbon-footprint/common' +import MongoDbCacheManager from '@cloud-carbon-footprint/app/src/MongoDbCacheManager' const port = process.env.PORT || 4000 const httpApp = express() -const serverLogger = new Logger('server') +const serverLogger = new Logger('Server') if (process.env.NODE_ENV === 'production') { httpApp.use(auth) @@ -24,6 +25,11 @@ if (process.env.NODE_ENV === 'production') { httpApp.use(helmet()) +// Establish Mongo Connection if cache method selected +if (configLoader()?.CACHE_MODE === 'MONGODB') { + MongoDbCacheManager.createDbConnection() +} + if (process.env.ENABLE_CORS) { const corsOptions: CorsOptions = { optionsSuccessStatus: 200, @@ -46,3 +52,13 @@ httpApp.listen(port, () => `Cloud Carbon Footprint Server listening at http://localhost:${port}`, ), ) + +// Instructions for graceful shutdown +process.on('SIGINT', async () => { + if (configLoader()?.CACHE_MODE === 'MONGODB') { + await MongoDbCacheManager.mongoClient.close() + serverLogger.info('\nMongoDB connection closed') + } + serverLogger.info('Cloud Carbon Footprint Server shutting down...') + process.exit() +}) From e640034ef9e804b964fa2a405004c98e55fc3d8d Mon Sep 17 00:00:00 2001 From: Arik Smith Date: Thu, 8 Jun 2023 21:17:44 -0400 Subject: [PATCH 127/251] add mongodb connection management to cli processes --- .../cli/src/SeedCacheFile/seedCacheFile.ts | 16 ++++++- .../SeedCacheFile/seedCacheFile.test.ts | 44 +++++++++++++++++++ packages/cli/src/__tests__/cli.test.ts | 24 ++++++++++ packages/cli/src/cli.ts | 17 ++++++- 4 files changed, 98 insertions(+), 3 deletions(-) diff --git a/packages/cli/src/SeedCacheFile/seedCacheFile.ts b/packages/cli/src/SeedCacheFile/seedCacheFile.ts index 0594122e7..2249089c8 100644 --- a/packages/cli/src/SeedCacheFile/seedCacheFile.ts +++ b/packages/cli/src/SeedCacheFile/seedCacheFile.ts @@ -4,7 +4,12 @@ import moment, { Moment } from 'moment' import { inputPrompt, listPrompt } from '../common' -import { App, createValidFootprintRequest } from '@cloud-carbon-footprint/app' +import { + App, + createValidFootprintRequest, + MongoDbCacheManager, +} from '@cloud-carbon-footprint/app' +import { configLoader } from '@cloud-carbon-footprint/common' /** * Logs the progress of the estimate request based on the given date range @@ -73,6 +78,10 @@ export default async function seedCacheFile(): Promise { }...`, ) + if (configLoader().CACHE_MODE === 'MONGODB') { + await MongoDbCacheManager.createDbConnection() + } + // Makes getCostAndEstimates requests in chunks based on request method and day frequency while (currentDate.isSameOrBefore(endDate)) { // Use the current date window as the inclusive start/end date of the request @@ -92,6 +101,11 @@ export default async function seedCacheFile(): Promise { currentDate.add(daysPerRequest, 'day') } + if (configLoader().CACHE_MODE === 'MONGODB') { + await MongoDbCacheManager.mongoClient.close() + console.info('MongoDB connection closed') + } + console.info( `Done! Estimates have been successfully seeded to the cache file!`, ) diff --git a/packages/cli/src/__tests__/SeedCacheFile/seedCacheFile.test.ts b/packages/cli/src/__tests__/SeedCacheFile/seedCacheFile.test.ts index 76981d294..c64dc146a 100644 --- a/packages/cli/src/__tests__/SeedCacheFile/seedCacheFile.test.ts +++ b/packages/cli/src/__tests__/SeedCacheFile/seedCacheFile.test.ts @@ -3,11 +3,13 @@ */ import { + configLoader, EstimationRequestValidationError, EstimationResult, } from '@cloud-carbon-footprint/common' import seedCacheFile from '../../SeedCacheFile/seedCacheFile' import * as common from '../../common' +import { MongoDbCacheManager } from '@cloud-carbon-footprint/app' const mockGetCostAndEstimates = jest.fn() jest.mock('../../common') @@ -29,6 +31,16 @@ jest.mock('@cloud-carbon-footprint/app', () => ({ cache: jest.fn(), })) +jest.mock('@cloud-carbon-footprint/common', () => ({ + ...(jest.requireActual('@cloud-carbon-footprint/common') as Record< + string, + unknown + >), + configLoader: jest.fn().mockImplementation(() => ({ + CACHE_MODE: '', + })), +})) + describe('seedCacheFile', () => { beforeEach(() => { console.info = jest.fn() @@ -220,4 +232,36 @@ describe('seedCacheFile', () => { }) }) }) + + describe('database connection', () => { + it('should initialize MongoDB client when it is configured as the cache mode', async () => { + ;(configLoader as jest.Mock).mockReturnValue({ + ...configLoader(), + CACHE_MODE: 'MONGODB', + }) + + const mockCreateDbConnection = jest.fn() + const mockMongoClient: any = { close: jest.fn() } + + MongoDbCacheManager.createDbConnection = mockCreateDbConnection + MongoDbCacheManager.mongoClient = mockMongoClient + + mockInputPrompts + .mockResolvedValueOnce('2020-07-01') + .mockResolvedValueOnce('2020-07-07') + .mockResolvedValueOnce('') + + mockListPrompts + .mockResolvedValueOnce('day') + .mockResolvedValueOnce('single') + + const mockResponse: EstimationResult[] = [] + mockGetCostAndEstimates.mockResolvedValueOnce(mockResponse) + + await seedCacheFile() + + expect(mockCreateDbConnection).toHaveBeenCalledTimes(1) + expect(mockMongoClient.close).toHaveBeenCalledTimes(1) + }) + }) }) diff --git a/packages/cli/src/__tests__/cli.test.ts b/packages/cli/src/__tests__/cli.test.ts index f5b067c50..29dc6d609 100644 --- a/packages/cli/src/__tests__/cli.test.ts +++ b/packages/cli/src/__tests__/cli.test.ts @@ -8,6 +8,7 @@ import { EstimationRequestValidationError, EstimationResult, GroupBy, + configLoader, } from '@cloud-carbon-footprint/common' import { mockAwsCloudWatchGetMetricData, @@ -21,6 +22,7 @@ import { mockVCPUTimeSeries, } from './fixtures/cloudmonitoring.fixtures' import cli from '../cli' +import { MongoDbCacheManager } from '@cloud-carbon-footprint/app' let mockWarn: jest.SpyInstance const mockListTimeSeries = jest.fn() @@ -286,4 +288,26 @@ describe('cli', () => { }) }) }) + + describe('database connection', () => { + const mockCreateDbConnection = jest.fn() + const mockClient: any = { close: jest.fn() } + beforeEach(() => { + MongoDbCacheManager.createDbConnection = mockCreateDbConnection + MongoDbCacheManager.mongoClient = mockClient + ;(configLoader as jest.Mock).mockReturnValue({ + ...configLoader(), + CACHE_MODE: 'MONGODB', + }) + }) + + it('should initialize MongoDB client when it is configured as the cache mode', async () => { + const consoleSpy = jest.spyOn(console, 'log').mockImplementation() + + await cli([...rawRequest]) + expect(mockCreateDbConnection).toHaveBeenCalledTimes(1) + expect(mockClient.close).toHaveBeenCalledTimes(1) + expect(consoleSpy).toHaveBeenCalledWith('MongoDB connection closed') + }) + }) }) diff --git a/packages/cli/src/cli.ts b/packages/cli/src/cli.ts index ac632fb9e..0e3d340e7 100644 --- a/packages/cli/src/cli.ts +++ b/packages/cli/src/cli.ts @@ -7,8 +7,12 @@ import moment from 'moment' import path from 'path' import * as process from 'process' -import { App, createValidFootprintRequest } from '@cloud-carbon-footprint/app' -import { EstimationResult } from '@cloud-carbon-footprint/common' +import { + App, + createValidFootprintRequest, + MongoDbCacheManager, +} from '@cloud-carbon-footprint/app' +import { EstimationResult, configLoader } from '@cloud-carbon-footprint/common' import EmissionsByDayAndServiceTable from './EmissionsByDayAndServiceTable' import EmissionsByServiceTable from './EmissionsByServiceTable' @@ -64,6 +68,10 @@ export default async function cli(argv: string[] = process.argv) { groupBy: 'day', // So that estimates are cached the same regardless of table grouping method }) + if (configLoader().CACHE_MODE === 'MONGODB') { + await MongoDbCacheManager.createDbConnection() + } + const { table, colWidths } = await new App() .getCostAndEstimates(estimationRequest) .then((estimations: EstimationResult[]) => { @@ -76,6 +84,11 @@ export default async function cli(argv: string[] = process.argv) { return EmissionsByDayAndServiceTable(estimations) }) + if (configLoader().CACHE_MODE === 'MONGODB') { + await MongoDbCacheManager.mongoClient.close() + console.log('MongoDB connection closed') + } + if (format === 'csv') { const filePath = path.join( process.cwd(), From 8995b8a7f29fb06f8a437166d32e75b1ed147870 Mon Sep 17 00:00:00 2001 From: Arik Smith Date: Thu, 8 Jun 2023 21:20:34 -0400 Subject: [PATCH 128/251] update create-app templates --- .../default-app/packages/api/src/api.ts | 2 +- .../default-app/packages/api/src/server.ts | 20 +++++++++++++++++-- .../cli/src/SeedCacheFile/seedCacheFile.ts | 16 ++++++++++++++- .../default-app/packages/cli/src/cli.ts | 17 ++++++++++++++-- 4 files changed, 49 insertions(+), 6 deletions(-) diff --git a/packages/create-app/templates/default-app/packages/api/src/api.ts b/packages/create-app/templates/default-app/packages/api/src/api.ts index faaf1313e..b39bc3184 100644 --- a/packages/create-app/templates/default-app/packages/api/src/api.ts +++ b/packages/create-app/templates/default-app/packages/api/src/api.ts @@ -22,7 +22,7 @@ import { RecommendationsRequestValidationError, } from '@cloud-carbon-footprint/common' -const apiLogger = new Logger('api') +const apiLogger = new Logger('API') /** * Returns the raw estimates diff --git a/packages/create-app/templates/default-app/packages/api/src/server.ts b/packages/create-app/templates/default-app/packages/api/src/server.ts index 8559303f8..617f073f3 100644 --- a/packages/create-app/templates/default-app/packages/api/src/server.ts +++ b/packages/create-app/templates/default-app/packages/api/src/server.ts @@ -12,11 +12,12 @@ import cors, { CorsOptions } from 'cors' import { createRouter } from './api' import auth from './auth' -import { Logger } from '@cloud-carbon-footprint/common' +import { Logger, configLoader } from '@cloud-carbon-footprint/common' +import MongoDbCacheManager from '@cloud-carbon-footprint/app/src/MongoDbCacheManager' const port = process.env.PORT || 4000 const httpApp = express() -const serverLogger = new Logger('server') +const serverLogger = new Logger('Server') if (process.env.NODE_ENV === 'production') { httpApp.use(auth) @@ -24,6 +25,11 @@ if (process.env.NODE_ENV === 'production') { httpApp.use(helmet()) +// Establish Mongo Connection if cache method selected +if (configLoader()?.CACHE_MODE === 'MONGODB') { + MongoDbCacheManager.createDbConnection() +} + if (process.env.ENABLE_CORS) { const corsOptions: CorsOptions = { optionsSuccessStatus: 200, @@ -46,3 +52,13 @@ httpApp.listen(port, () => `Cloud Carbon Footprint Server listening at http://localhost:${port}`, ), ) + +// Instructions for graceful shutdown +process.on('SIGINT', async () => { + if (configLoader()?.CACHE_MODE === 'MONGODB') { + await MongoDbCacheManager.mongoClient.close() + serverLogger.info('\nMongoDB connection closed') + } + serverLogger.info('Cloud Carbon Footprint Server shutting down...') + process.exit() +}) diff --git a/packages/create-app/templates/default-app/packages/cli/src/SeedCacheFile/seedCacheFile.ts b/packages/create-app/templates/default-app/packages/cli/src/SeedCacheFile/seedCacheFile.ts index 0594122e7..2249089c8 100644 --- a/packages/create-app/templates/default-app/packages/cli/src/SeedCacheFile/seedCacheFile.ts +++ b/packages/create-app/templates/default-app/packages/cli/src/SeedCacheFile/seedCacheFile.ts @@ -4,7 +4,12 @@ import moment, { Moment } from 'moment' import { inputPrompt, listPrompt } from '../common' -import { App, createValidFootprintRequest } from '@cloud-carbon-footprint/app' +import { + App, + createValidFootprintRequest, + MongoDbCacheManager, +} from '@cloud-carbon-footprint/app' +import { configLoader } from '@cloud-carbon-footprint/common' /** * Logs the progress of the estimate request based on the given date range @@ -73,6 +78,10 @@ export default async function seedCacheFile(): Promise { }...`, ) + if (configLoader().CACHE_MODE === 'MONGODB') { + await MongoDbCacheManager.createDbConnection() + } + // Makes getCostAndEstimates requests in chunks based on request method and day frequency while (currentDate.isSameOrBefore(endDate)) { // Use the current date window as the inclusive start/end date of the request @@ -92,6 +101,11 @@ export default async function seedCacheFile(): Promise { currentDate.add(daysPerRequest, 'day') } + if (configLoader().CACHE_MODE === 'MONGODB') { + await MongoDbCacheManager.mongoClient.close() + console.info('MongoDB connection closed') + } + console.info( `Done! Estimates have been successfully seeded to the cache file!`, ) diff --git a/packages/create-app/templates/default-app/packages/cli/src/cli.ts b/packages/create-app/templates/default-app/packages/cli/src/cli.ts index 470950512..ecbe8494f 100644 --- a/packages/create-app/templates/default-app/packages/cli/src/cli.ts +++ b/packages/create-app/templates/default-app/packages/cli/src/cli.ts @@ -7,8 +7,12 @@ import moment from 'moment' import path from 'path' import * as process from 'process' -import { App, createValidFootprintRequest } from '@cloud-carbon-footprint/app' -import { EstimationResult } from '@cloud-carbon-footprint/common' +import { + App, + createValidFootprintRequest, + MongoDbCacheManager, +} from '@cloud-carbon-footprint/app' +import { EstimationResult, configLoader } from '@cloud-carbon-footprint/common' import EmissionsByDayAndServiceTable from './EmissionsByDayAndServiceTable' import EmissionsByServiceTable from './EmissionsByServiceTable' @@ -64,6 +68,10 @@ export default async function cli(argv: string[] = process.argv) { groupBy: 'day', // So that estimates are cached the same regardless of table grouping method }) + if (configLoader().CACHE_MODE === 'MONGODB') { + await MongoDbCacheManager.createDbConnection() + } + const { table, colWidths } = await new App() .getCostAndEstimates(estimationRequest) .then((estimations: EstimationResult[]) => { @@ -76,6 +84,11 @@ export default async function cli(argv: string[] = process.argv) { return EmissionsByDayAndServiceTable(estimations) }) + if (configLoader().CACHE_MODE === 'MONGODB') { + await MongoDbCacheManager.mongoClient.close() + console.log('MongoDB connection closed') + } + if (format === 'csv') { const filePath = path.join( process.cwd(), From fedf79c0b07b421c602c4ed87c2cd3c5cfd4b9be Mon Sep 17 00:00:00 2001 From: Arik Smith Date: Thu, 8 Jun 2023 21:28:13 -0400 Subject: [PATCH 129/251] changeset: API and CLI processes now persist connection to mongodb client when enabled as cache --- .changeset/rare-parents-know.md | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 .changeset/rare-parents-know.md diff --git a/.changeset/rare-parents-know.md b/.changeset/rare-parents-know.md new file mode 100644 index 000000000..d54e6a784 --- /dev/null +++ b/.changeset/rare-parents-know.md @@ -0,0 +1,10 @@ +--- +'@cloud-carbon-footprint/api': minor +'@cloud-carbon-footprint/app': minor +'@cloud-carbon-footprint/cli': minor +'@cloud-carbon-footprint/create-app': minor +--- + +API and CLI processes now persist connection to mongodb client when enabled as cache + +For Create-App changes, please refer to this [commit](https://github.com/cloud-carbon-footprint/cloud-carbon-footprint/commit/8995b8a7f29fb06f8a437166d32e75b1ed147870). \ No newline at end of file From 5069d8bc16e4ab93dc0b20f49d01bf6d61c5717f Mon Sep 17 00:00:00 2001 From: Arik Smith Date: Tue, 13 Jun 2023 10:33:52 -0400 Subject: [PATCH 130/251] [#1022] add support for specifying list of Azure subscriptions to fetch --- .../azure/src/__tests__/AzureAccount.test.ts | 78 ++++++++++++++++++- .../azure/src/application/AzureAccount.ts | 31 +++++++- packages/common/src/Config.ts | 8 ++ packages/common/src/__tests__/Config.test.ts | 7 ++ 4 files changed, 119 insertions(+), 5 deletions(-) diff --git a/packages/azure/src/__tests__/AzureAccount.test.ts b/packages/azure/src/__tests__/AzureAccount.test.ts index 1e7e5eaf4..c7ab527cd 100644 --- a/packages/azure/src/__tests__/AzureAccount.test.ts +++ b/packages/azure/src/__tests__/AzureAccount.test.ts @@ -15,7 +15,7 @@ import AzureCredentialsProvider from '../application/AzureCredentialsProvider' import ConsumptionManagementService from '../lib/ConsumptionManagement' import AdvisorRecommendations from '../lib/AdvisorRecommendations' -const mockListSubscriptions = { list: jest.fn() } +const mockListSubscriptions = { list: jest.fn(), get: jest.fn() } jest.mock('@azure/arm-resources-subscriptions', () => { return { @@ -219,7 +219,81 @@ describe('Azure Account', () => { expect(promiseSpy).toHaveBeenCalledTimes(3) // Reset the config - AZURE.SUBSCRIPTION_CHUNKS = 0 + delete AZURE.SUBSCRIPTION_CHUNKS + }) + + it('fetches data only for specific subscriptions when specified subscriptions are set', async () => { + const mockCredentials = { + clientId: 'test-client-id', + secret: 'test-client-secret', + domain: 'test-tenant-id', + } + ;(createCredentialsSpy as jest.Mock).mockResolvedValue(mockCredentials) + + const AZURE: any = configLoader().AZURE || {} + const subscriptions = ['sub-1', 'sub-2', 'sub-3'] + AZURE.SUBSCRIPTIONS = subscriptions + + subscriptions.forEach((subscriptionId) => { + mockListSubscriptions.get.mockReturnValueOnce({ subscriptionId }) + }) + + const mockEstimates: EstimationResult[] = [ + { + timestamp: startDate, + serviceEstimates: [ + { + kilowattHours: 0.09313874999999999, + co2e: 0.000021235635, + usesAverageCPUConstant: true, + cloudProvider: 'AZURE', + accountId: testAccountId, + accountName: testAccountName, + serviceName: 'Virtual Machines', + cost: 5, + region: 'UK South', + }, + ], + periodStartDate: startDate, + periodEndDate: endDate, + groupBy: grouping, + }, + ] + + ;(getEstimatesSpy as jest.Mock).mockResolvedValue(mockEstimates) + const getDataForSubscriptionSpy = jest.spyOn( + AzureAccount.prototype, + 'getDataForSubscription', + ) + + // when + const azureAccount = new AzureAccount() + await azureAccount.initializeAccount() + const results = await azureAccount.getDataFromConsumptionManagement( + startDate, + endDate, + grouping, + ) + + // Results should be the same as the mockEstimates array repeated35 times + const expectedEstimates = Array.from( + { length: 3 }, + () => mockEstimates[0], + ) + + expect(results).toEqual(expectedEstimates) + // getDataForSubscription should have been called 3 times (once for each given subscription ID) + subscriptions.forEach((subscriptionId) => { + expect(getDataForSubscriptionSpy).toHaveBeenCalledWith( + startDate, + endDate, + subscriptionId, + grouping, + ) + }) + + // Reset the config + delete AZURE.SUBSCRIPTIONS }) }) }) diff --git a/packages/azure/src/application/AzureAccount.ts b/packages/azure/src/application/AzureAccount.ts index 77885942d..39c828259 100644 --- a/packages/azure/src/application/AzureAccount.ts +++ b/packages/azure/src/application/AzureAccount.ts @@ -80,10 +80,12 @@ export default class AzureAccount extends CloudProviderAccount { endDate: Date, grouping: GroupBy, ): Promise { - const subscriptions = await this.getSubscriptions() - const AZURE = configLoader().AZURE + const subscriptions = AZURE.SUBSCRIPTIONS?.length + ? await this.getSubscriptionsByIds(AZURE.SUBSCRIPTIONS) + : await this.getSubscriptions() + const requests = this.createSubscriptionRequests( subscriptions, startDate, @@ -105,7 +107,9 @@ export default class AzureAccount extends CloudProviderAccount { ? R.splitEvery(AZURE.SUBSCRIPTION_CHUNKS, requests) : [requests] this.logger.debug( - `Fetching Azure consumption data with ${AZURE.SUBSCRIPTION_CHUNKS}} 1} chunk(s)`, + `Fetching Azure consumption data with ${ + AZURE.SUBSCRIPTION_CHUNKS || 1 + } chunk(s)`, ) // TODO: Remove before release. @@ -139,6 +143,27 @@ export default class AzureAccount extends CloudProviderAccount { return subscriptions } + private async getSubscriptionsByIds( + subscriptionIds: string[], + ): Promise { + const subscriptions = [] + + for (const subscriptionId of subscriptionIds) { + try { + const subscription = await this.subscriptionClient.subscriptions.get( + subscriptionId, + ) + subscriptions.push(subscription) + } catch (error) { + this.logger.warn( + `Unable to fetch subscription details for: "${subscriptionId}". Reason: ${error.message}`, + ) + } + } + + return subscriptions + } + static getDataFromConsumptionManagementInputData( inputData: LookupTableInput[], ) { diff --git a/packages/common/src/Config.ts b/packages/common/src/Config.ts index b5c004be5..bc4b596db 100644 --- a/packages/common/src/Config.ts +++ b/packages/common/src/Config.ts @@ -60,6 +60,7 @@ export interface CCFConfig { RESOURCE_TAG_NAMES?: string[] CONSUMPTION_CHUNKS_DAYS?: number SUBSCRIPTION_CHUNKS?: number + SUBSCRIPTIONS: string[] } LOGGING_MODE?: string CACHE_MODE?: string @@ -132,6 +133,12 @@ const getGCPProjects = () => { return process.env.GCP_PROJECTS ? process.env.GCP_PROJECTS : '[]' } +const getAzureSubscriptions = () => { + return process.env.AZURE_SUBSCRIPTIONS + ? process.env.AZURE_SUBSCRIPTIONS + : '[]' +} + // This function allows support for using Docker Secrets. const getEnvVar = (envVar: string): string => { try { @@ -267,6 +274,7 @@ const getConfig = (): CCFConfig => ({ SUBSCRIPTION_CHUNKS: parseInt( getEnvVar('AZURE_SUBSCRIPTION_CHUNKS') || '0', ), + SUBSCRIPTIONS: JSON.parse(getAzureSubscriptions()) || [], }, LOGGING_MODE: process.env.LOGGING_MODE || '', CACHE_MODE: getEnvVar('CACHE_MODE') || '', diff --git a/packages/common/src/__tests__/Config.test.ts b/packages/common/src/__tests__/Config.test.ts index 7842b80a0..4a3f7b8a3 100644 --- a/packages/common/src/__tests__/Config.test.ts +++ b/packages/common/src/__tests__/Config.test.ts @@ -92,5 +92,12 @@ describe('Config', () => { expect(config.AZURE.RESOURCE_TAG_NAMES).toEqual(['Environment']) }) }) + + it('loads list of Azure subscriptions from environment variables', () => { + withEnvironment('AZURE_SUBSCRIPTIONS', `["sub-1", "sub-2"]`, () => { + const config = getConfig() + expect(config.AZURE.SUBSCRIPTIONS).toEqual(['sub-1', 'sub-2']) + }) + }) }) }) From 1a5d7636d43f8d26e371eb603a19ca5b695a8df4 Mon Sep 17 00:00:00 2001 From: Arik Smith Date: Tue, 13 Jun 2023 10:40:55 -0400 Subject: [PATCH 131/251] changeset: Adds support for listing Azure subscription IDs to fetch --- .changeset/dry-mails-cough.md | 6 ++++++ .changeset/light-socks-add.md | 5 +++++ 2 files changed, 11 insertions(+) create mode 100644 .changeset/dry-mails-cough.md create mode 100644 .changeset/light-socks-add.md diff --git a/.changeset/dry-mails-cough.md b/.changeset/dry-mails-cough.md new file mode 100644 index 000000000..6745617b0 --- /dev/null +++ b/.changeset/dry-mails-cough.md @@ -0,0 +1,6 @@ +--- +'@cloud-carbon-footprint/azure': minor +'@cloud-carbon-footprint/common': minor +--- + +Adds support for listing Azure subscription IDs to fetch diff --git a/.changeset/light-socks-add.md b/.changeset/light-socks-add.md new file mode 100644 index 000000000..6986793ca --- /dev/null +++ b/.changeset/light-socks-add.md @@ -0,0 +1,5 @@ +--- +'@cloud-carbon-footprint/cli': patch +--- + +Adds support for listing Azure subscription IDs to fetch From c9f02a82cac2372a21507501a97b39b1638fa2cf Mon Sep 17 00:00:00 2001 From: Arik Smith Date: Tue, 13 Jun 2023 10:41:29 -0400 Subject: [PATCH 132/251] update env templates with azure subscription config --- packages/api/.env.template | 3 +++ packages/cli/.env.template | 3 +++ 2 files changed, 6 insertions(+) diff --git a/packages/api/.env.template b/packages/api/.env.template index b73b3fd3e..3e48e9f5f 100644 --- a/packages/api/.env.template +++ b/packages/api/.env.template @@ -71,6 +71,9 @@ AZURE_CONSUMPTION_CHUNKS_DAYS=0 # To avoid rate limiting, asynchronous consumption management calls can be chunked by subscription AZURE_SUBSCRIPTION_CHUNKS=0 +# List of Azure subscriptions to include in estimations (all subscriptions are fetched by default) +AZURE_SUBSCRIPTIONS=["subscription-1", "subscription 2"] + # Cache # Optionally set which cache you are using (defaults to local) diff --git a/packages/cli/.env.template b/packages/cli/.env.template index 0e006f086..549788f94 100644 --- a/packages/cli/.env.template +++ b/packages/cli/.env.template @@ -72,6 +72,9 @@ AZURE_CONSUMPTION_CHUNKS_DAYS=0 # To avoid rate limiting, asynchronous consumption management calls can be chunked by subscription (takes precedence over AZURE_SUBSCRIPTION_CHUNKS) AZURE_SUBSCRIPTION_CHUNKS=0 +# List of Azure subscriptions to include in estimations (all subscriptions are fetched by default) +AZURE_SUBSCRIPTIONS=["subscription-1", "subscription 2"] + # Cache # Optionally set which cache you are using (defaults to local) From e29a98c42d36491f7eab21605ba36c608eb00ad1 Mon Sep 17 00:00:00 2001 From: Arik Smith Date: Tue, 13 Jun 2023 11:24:16 -0400 Subject: [PATCH 133/251] remove swagger as a root dependency --- package.json | 3 - yarn.lock | 216 +++++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 208 insertions(+), 11 deletions(-) diff --git a/package.json b/package.json index 55add261f..52507fe06 100644 --- a/package.json +++ b/package.json @@ -57,8 +57,5 @@ "release": "changeset version && yarn install", "postinstall": "husky install", "version": "yarn changeset version && yarn install --no-immutable" - }, - "dependencies": { - "swagger-jsdoc": "^6.2.8" } } diff --git a/yarn.lock b/yarn.lock index 4481528f6..856a7c499 100644 --- a/yarn.lock +++ b/yarn.lock @@ -25,6 +25,48 @@ __metadata: languageName: node linkType: hard +"@apidevtools/json-schema-ref-parser@npm:^9.0.6": + version: 9.1.2 + resolution: "@apidevtools/json-schema-ref-parser@npm:9.1.2" + dependencies: + "@jsdevtools/ono": ^7.1.3 + "@types/json-schema": ^7.0.6 + call-me-maybe: ^1.0.1 + js-yaml: ^4.1.0 + checksum: 5bd6885db0fd6633879bb4638b7a3aead6b061cb6422083c6be505ee6873be54e3376380df164c73edd8901d4145a9bfe9bc0b008a568fd8b0626b1df96fae8f + languageName: node + linkType: hard + +"@apidevtools/openapi-schemas@npm:^2.0.4": + version: 2.1.0 + resolution: "@apidevtools/openapi-schemas@npm:2.1.0" + checksum: 4a8f64935b9049ef21e41fa4b188f39f6bc3f5291cebd451701db1115451ccb246a739e46cc5ce9ecdec781671431db40db7851acdac84a990a45756e0f32de3 + languageName: node + linkType: hard + +"@apidevtools/swagger-methods@npm:^3.0.2": + version: 3.0.2 + resolution: "@apidevtools/swagger-methods@npm:3.0.2" + checksum: d06b1ac5c1956613c4c6be695612ef860cd4e962b93a509ca551735a328a856cae1e33399cac1dcbf8333ba22b231746f3586074769ef0e172cf549ec9e7eaae + languageName: node + linkType: hard + +"@apidevtools/swagger-parser@npm:10.0.3": + version: 10.0.3 + resolution: "@apidevtools/swagger-parser@npm:10.0.3" + dependencies: + "@apidevtools/json-schema-ref-parser": ^9.0.6 + "@apidevtools/openapi-schemas": ^2.0.4 + "@apidevtools/swagger-methods": ^3.0.2 + "@jsdevtools/ono": ^7.1.3 + call-me-maybe: ^1.0.1 + z-schema: ^5.0.1 + peerDependencies: + openapi-types: ">=7" + checksum: 580a86d4c2a667e825dae80d2ed115c282e6210e3cb0ffc39b3bf5fc5b4fcb576cce3fc3d0519b6c4de8fa617ab0dbee1adeac134d0c1364e0a0f7d394b28796 + languageName: node + linkType: hard + "@azure/abort-controller@npm:^1.0.0": version: 1.1.0 resolution: "@azure/abort-controller@npm:1.1.0" @@ -2164,6 +2206,8 @@ __metadata: "@types/node": ^17.0.8 "@types/source-map-support": ^0.5.3 "@types/supertest": ^2.0.11 + "@types/swagger-jsdoc": ^6.0.1 + "@types/swagger-ui-express": ^4.1.3 "@typescript-eslint/eslint-plugin": ^5.9.0 "@typescript-eslint/parser": ^5.9.0 cors: ^2.8.5 @@ -2182,6 +2226,8 @@ __metadata: rimraf: ^3.0.2 source-map-support: ^0.5.19 supertest: ^6.1.3 + swagger-jsdoc: ^6.2.8 + swagger-ui-express: ^4.6.2 ts-jest: ^27.1.2 ts-node: ^10.4.0 ts-node-dev: ^2.0.0 @@ -3567,6 +3613,13 @@ __metadata: languageName: node linkType: hard +"@jsdevtools/ono@npm:^7.1.3": + version: 7.1.3 + resolution: "@jsdevtools/ono@npm:7.1.3" + checksum: 2297fcd472ba810bffe8519d2249171132844c7174f3a16634f9260761c8c78bc0428a4190b5b6d72d45673c13918ab9844d706c3ed4ef8f62ab11a2627a08ad + languageName: node + linkType: hard + "@leichtgewicht/ip-codec@npm:^2.0.1": version: 2.0.4 resolution: "@leichtgewicht/ip-codec@npm:2.0.4" @@ -5899,6 +5952,13 @@ __metadata: languageName: node linkType: hard +"@types/json-schema@npm:^7.0.6": + version: 7.0.12 + resolution: "@types/json-schema@npm:7.0.12" + checksum: 00239e97234eeb5ceefb0c1875d98ade6e922bfec39dd365ec6bd360b5c2f825e612ac4f6e5f1d13601b8b30f378f15e6faa805a3a732f4a1bbe61915163d293 + languageName: node + linkType: hard + "@types/json5@npm:^0.0.29": version: 0.0.29 resolution: "@types/json5@npm:0.0.29" @@ -6258,6 +6318,23 @@ __metadata: languageName: node linkType: hard +"@types/swagger-jsdoc@npm:^6.0.1": + version: 6.0.1 + resolution: "@types/swagger-jsdoc@npm:6.0.1" + checksum: c15fcbf7415d7da6bfe908a44160a96ed9706844f74187f3424a6e65f822e08bc4f26e0fffd3bedb0f1eff60d8618952d105f31c0742df8c94e6c6b0fa9de59a + languageName: node + linkType: hard + +"@types/swagger-ui-express@npm:^4.1.3": + version: 4.1.3 + resolution: "@types/swagger-ui-express@npm:4.1.3" + dependencies: + "@types/express": "*" + "@types/serve-static": "*" + checksum: 1c990fa8c158f699c5443245383daef2c4b47efce715c105e67f9b88447cf23663774393fdeea7d5a6e97a83d6959b09129f19c4abca64abdb7db74517162295 + languageName: node + linkType: hard + "@types/testing-library__jest-dom@npm:^5.9.1": version: 5.14.5 resolution: "@types/testing-library__jest-dom@npm:5.14.5" @@ -8157,6 +8234,13 @@ __metadata: languageName: node linkType: hard +"call-me-maybe@npm:^1.0.1": + version: 1.0.2 + resolution: "call-me-maybe@npm:1.0.2" + checksum: 42ff2d0bed5b207e3f0122589162eaaa47ba618f79ad2382fe0ba14d9e49fbf901099a6227440acc5946f86a4953e8aa2d242b330b0a5de4d090bb18f8935cae + languageName: node + linkType: hard + "callsite-record@npm:^4.0.0": version: 4.1.4 resolution: "callsite-record@npm:4.1.4" @@ -8765,6 +8849,13 @@ __metadata: languageName: node linkType: hard +"commander@npm:6.2.0": + version: 6.2.0 + resolution: "commander@npm:6.2.0" + checksum: 59baef90f07ba8ae941e9d41a0a15be721d69e948ff833c218d936ffbbe2141580ce1ae2b1103c41436c30eefd1eb7bc3285de8936cd283fb78409f54a93f45a + languageName: node + linkType: hard + "commander@npm:^10.0.0": version: 10.0.0 resolution: "commander@npm:10.0.0" @@ -8800,6 +8891,13 @@ __metadata: languageName: node linkType: hard +"commander@npm:^9.4.1": + version: 9.5.0 + resolution: "commander@npm:9.5.0" + checksum: c7a3e27aa59e913b54a1bafd366b88650bc41d6651f0cbe258d4ff09d43d6a7394232a4dadd0bf518b3e696fdf595db1028a0d82c785b88bd61f8a440cecfade + languageName: node + linkType: hard + "common-path-prefix@npm:^3.0.0": version: 3.0.0 resolution: "common-path-prefix@npm:3.0.0" @@ -10142,21 +10240,21 @@ __metadata: languageName: node linkType: hard -"doctrine@npm:^2.1.0": - version: 2.1.0 - resolution: "doctrine@npm:2.1.0" +"doctrine@npm:3.0.0, doctrine@npm:^3.0.0": + version: 3.0.0 + resolution: "doctrine@npm:3.0.0" dependencies: esutils: ^2.0.2 - checksum: a45e277f7feaed309fe658ace1ff286c6e2002ac515af0aaf37145b8baa96e49899638c7cd47dccf84c3d32abfc113246625b3ac8f552d1046072adee13b0dc8 + checksum: fd7673ca77fe26cd5cba38d816bc72d641f500f1f9b25b83e8ce28827fe2da7ad583a8da26ab6af85f834138cf8dae9f69b0cd6ab925f52ddab1754db44d99ce languageName: node linkType: hard -"doctrine@npm:^3.0.0": - version: 3.0.0 - resolution: "doctrine@npm:3.0.0" +"doctrine@npm:^2.1.0": + version: 2.1.0 + resolution: "doctrine@npm:2.1.0" dependencies: esutils: ^2.0.2 - checksum: fd7673ca77fe26cd5cba38d816bc72d641f500f1f9b25b83e8ce28827fe2da7ad583a8da26ab6af85f834138cf8dae9f69b0cd6ab925f52ddab1754db44d99ce + checksum: a45e277f7feaed309fe658ace1ff286c6e2002ac515af0aaf37145b8baa96e49899638c7cd47dccf84c3d32abfc113246625b3ac8f552d1046072adee13b0dc8 languageName: node linkType: hard @@ -12163,6 +12261,20 @@ __metadata: languageName: node linkType: hard +"glob@npm:7.1.6": + version: 7.1.6 + resolution: "glob@npm:7.1.6" + dependencies: + fs.realpath: ^1.0.0 + inflight: ^1.0.4 + inherits: 2 + minimatch: ^3.0.4 + once: ^1.3.0 + path-is-absolute: ^1.0.0 + checksum: 351d549dd90553b87c2d3f90ce11aed9e1093c74130440e7ae0592e11bbcd2ce7f0ebb8ba6bfe63aaf9b62166a7f4c80cb84490ae5d78408bb2572bf7d4ee0a6 + languageName: node + linkType: hard + "glob@npm:^7.0.3, glob@npm:^7.0.5, glob@npm:^7.1.1, glob@npm:^7.1.2, glob@npm:^7.1.3, glob@npm:^7.1.4, glob@npm:^7.1.6": version: 7.2.3 resolution: "glob@npm:7.2.3" @@ -15326,6 +15438,13 @@ __metadata: languageName: node linkType: hard +"lodash.isequal@npm:^4.5.0": + version: 4.5.0 + resolution: "lodash.isequal@npm:4.5.0" + checksum: da27515dc5230eb1140ba65ff8de3613649620e8656b19a6270afe4866b7bd461d9ba2ac8a48dcc57f7adac4ee80e1de9f965d89d4d81a0ad52bb3eec2609644 + languageName: node + linkType: hard + "lodash.isinteger@npm:^4.0.4": version: 4.0.4 resolution: "lodash.isinteger@npm:4.0.4" @@ -15382,6 +15501,13 @@ __metadata: languageName: node linkType: hard +"lodash.mergewith@npm:^4.6.2": + version: 4.6.2 + resolution: "lodash.mergewith@npm:4.6.2" + checksum: a6db2a9339752411f21b956908c404ec1e088e783a65c8b29e30ae5b3b6384f82517662d6f425cc97c2070b546cc2c7daaa8d33f78db7b6e9be06cd834abdeb8 + languageName: node + linkType: hard + "lodash.once@npm:^4.0.0": version: 4.1.1 resolution: "lodash.once@npm:4.1.1" @@ -21501,6 +21627,49 @@ __metadata: languageName: node linkType: hard +"swagger-jsdoc@npm:^6.2.8": + version: 6.2.8 + resolution: "swagger-jsdoc@npm:6.2.8" + dependencies: + commander: 6.2.0 + doctrine: 3.0.0 + glob: 7.1.6 + lodash.mergewith: ^4.6.2 + swagger-parser: ^10.0.3 + yaml: 2.0.0-1 + bin: + swagger-jsdoc: bin/swagger-jsdoc.js + checksum: 465c7798f962c7f9eb532d6f886902ac50dab32f7d427bba1315a985e7b679e232a2917b1a8f7eef35f0fa3369dfba38176192b743f68cf417c1aecf0c0d7bf9 + languageName: node + linkType: hard + +"swagger-parser@npm:^10.0.3": + version: 10.0.3 + resolution: "swagger-parser@npm:10.0.3" + dependencies: + "@apidevtools/swagger-parser": 10.0.3 + checksum: bd99d94543b9bdc6bf062ea211f70d4fa004e6e8ce97ee47a388a046f3cc2839e7349970e8a0b11136b55a674e1cc7b0bbdfbe4fb40adf0c68bc70736eedb8b7 + languageName: node + linkType: hard + +"swagger-ui-dist@npm:>=4.11.0": + version: 5.0.0 + resolution: "swagger-ui-dist@npm:5.0.0" + checksum: 61e3eddccde87c10eb0bf6a159e43d58ef4ba0d7346747579cc7cab8d82222507a4ce676801834943ccb853126500bf4c6953041e5ff47a0dddad248398ca1e1 + languageName: node + linkType: hard + +"swagger-ui-express@npm:^4.6.2": + version: 4.6.3 + resolution: "swagger-ui-express@npm:4.6.3" + dependencies: + swagger-ui-dist: ">=4.11.0" + peerDependencies: + express: ">=4.0.0 || >=5.0.0-beta" + checksum: bd0e02d2572685fcd82701b29f27ba6a27bc72de2b1553e84f884d008a0bf85a3711c9e236bb658130d702892866744d1a8a30b52b887e4f8224635c57afc63d + languageName: node + linkType: hard + "symbol-tree@npm:^3.2.4": version: 3.2.4 resolution: "symbol-tree@npm:3.2.4" @@ -22946,6 +23115,13 @@ __metadata: languageName: node linkType: hard +"validator@npm:^13.7.0": + version: 13.9.0 + resolution: "validator@npm:13.9.0" + checksum: e2c936f041f61faa42bafd17c6faddf939498666cd82e88d733621c286893730b008959f4cb12ab3e236148a4f3805c30b85e3dcf5e0efd8b0cbcd36c02bfc0c + languageName: node + linkType: hard + "vary@npm:^1, vary@npm:~1.1.2": version: 1.1.2 resolution: "vary@npm:1.1.2" @@ -23848,6 +24024,13 @@ __metadata: languageName: node linkType: hard +"yaml@npm:2.0.0-1": + version: 2.0.0-1 + resolution: "yaml@npm:2.0.0-1" + checksum: ccfbd1424dbf205a8828593faa91dfd40bd21e3fb575e257280d3526ade9508f3ea0acbe7605abc81bac6b4d6729ddb5b3de6bd818232f48480fd1eda8d7fd4c + languageName: node + linkType: hard + "yaml@npm:^1.10.0, yaml@npm:^1.10.2, yaml@npm:^1.7.2": version: 1.10.2 resolution: "yaml@npm:1.10.2" @@ -23955,3 +24138,20 @@ __metadata: checksum: f77b3d8d00310def622123df93d4ee654fc6a0096182af8bd60679ddcdfb3474c56c6c7190817c84a2785648cdee9d721c0154eb45698c62176c322fb46fc700 languageName: node linkType: hard + +"z-schema@npm:^5.0.1": + version: 5.0.5 + resolution: "z-schema@npm:5.0.5" + dependencies: + commander: ^9.4.1 + lodash.get: ^4.4.2 + lodash.isequal: ^4.5.0 + validator: ^13.7.0 + dependenciesMeta: + commander: + optional: true + bin: + z-schema: bin/z-schema + checksum: 8a1d66817ae4384dc3f63311f0cccaadd95cc9640eaade5fd3fbf91aa80d6bb82fb95d9b9171fa82ac371a0155b32b7f5f77bbe84dabaca611b66f74c628f0b8 + languageName: node + linkType: hard From e396fffb9759f22dd9c4e8a70e36c709cdbcc206 Mon Sep 17 00:00:00 2001 From: Arik Smith Date: Tue, 13 Jun 2023 14:25:17 -0400 Subject: [PATCH 134/251] removes recommendation option schemas --- packages/common/src/RecommendationResult.ts | 48 +-------------------- 1 file changed, 1 insertion(+), 47 deletions(-) diff --git a/packages/common/src/RecommendationResult.ts b/packages/common/src/RecommendationResult.ts index 6117e4e69..ec546be37 100644 --- a/packages/common/src/RecommendationResult.ts +++ b/packages/common/src/RecommendationResult.ts @@ -11,7 +11,7 @@ * properties: * cloudProvider: * type: string - * description: aws, gcp, ... + * description: aws | gcp | azure * accountId: * type: string * accountName: @@ -56,22 +56,6 @@ export type ComputeOptimizerRecommendationOption = | EBSRecommendationOption | LambdaRecommendationOption -/** - * @openapi - * components: - * schemas: - * EC2Recommendation: - * type: object - * properties: - * instanceType: - * type: string - * costSavings: - * type: string - * performanceRisk: - * type: string - * vcpus: - * type: string - */ export type EC2RecommendationOption = { instanceType: string costSavings: string @@ -79,22 +63,6 @@ export type EC2RecommendationOption = { vcpus: string } -/** - * @openapi - * components: - * schemas: - * EBSRecommendation: - * type: object - * properties: - * volumeType: - * type: string - * volumeSize: - * type: string - * costSavings: - * type: string - * performanceRisk: - * type: string - */ export type EBSRecommendationOption = { volumeType: string volumeSize: string @@ -102,20 +70,6 @@ export type EBSRecommendationOption = { performanceRisk: string } -/** - * @openapi - * components: - * schemas: - * LambdaRecommendation: - * type: object - * properties: - * memorySize: - * type: string - * costSavings: - * type: string - * performanceRisk?: - * type: string - */ export type LambdaRecommendationOption = { memorySize: string costSavings: string From 709613417406e03c95ac3b84eeeea21c22ddb158 Mon Sep 17 00:00:00 2001 From: Arik Smith Date: Wed, 14 Jun 2023 12:44:27 -0400 Subject: [PATCH 135/251] [#1022] remove exclusive and synchronous behavior from azure day chunking --- .../azure/src/application/AzureAccount.ts | 20 ++----------------- 1 file changed, 2 insertions(+), 18 deletions(-) diff --git a/packages/azure/src/application/AzureAccount.ts b/packages/azure/src/application/AzureAccount.ts index 39c828259..7ef2de1aa 100644 --- a/packages/azure/src/application/AzureAccount.ts +++ b/packages/azure/src/application/AzureAccount.ts @@ -93,36 +93,20 @@ export default class AzureAccount extends CloudProviderAccount { grouping, ) - // If chunking by day is enabled, synchronously fetch each subscription - if (AZURE.CONSUMPTION_CHUNKS_DAYS) { - const estimationResults: Array> = [] - for (const request of requests) { - estimationResults.push(await request()) - } - return estimationResults.flat() - } - - // If chunking by day is disabled, asynchronously fetch all or chunked subscriptions + // Fetch subscriptions in configured chunks or 10 at a time by default. const chunkedRequests = AZURE.SUBSCRIPTION_CHUNKS ? R.splitEvery(AZURE.SUBSCRIPTION_CHUNKS, requests) : [requests] this.logger.debug( - `Fetching Azure consumption data with ${ - AZURE.SUBSCRIPTION_CHUNKS || 1 - } chunk(s)`, + `Fetching Azure consumption data with ${AZURE.SUBSCRIPTION_CHUNKS} chunk(s)`, ) - // TODO: Remove before release. - console.time(`Azure Subscriptions: ${AZURE.SUBSCRIPTION_CHUNKS} chunk(s)`) const estimationResults = [] for (const requests of chunkedRequests) { estimationResults.push( await Promise.all(requests.map(async (request) => request())), ) } - console.timeEnd( - `Azure Subscriptions: ${AZURE.SUBSCRIPTION_CHUNKS || 1} chunk(s)`, - ) return R.flatten(estimationResults) } From 86971b42e8516a4e2c3f52702bde550aa6cdfb39 Mon Sep 17 00:00:00 2001 From: Arik Smith Date: Wed, 14 Jun 2023 12:47:31 -0400 Subject: [PATCH 136/251] [#1022] set chunking of 10 subscriptions as default azure fetch behavior --- packages/common/src/Config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/common/src/Config.ts b/packages/common/src/Config.ts index bc4b596db..19255cfe0 100644 --- a/packages/common/src/Config.ts +++ b/packages/common/src/Config.ts @@ -272,7 +272,7 @@ const getConfig = (): CCFConfig => ({ getEnvVar('AZURE_CONSUMPTION_CHUNKS_DAYS') || '0', ), SUBSCRIPTION_CHUNKS: parseInt( - getEnvVar('AZURE_SUBSCRIPTION_CHUNKS') || '0', + getEnvVar('AZURE_SUBSCRIPTION_CHUNKS') || '10', ), SUBSCRIPTIONS: JSON.parse(getAzureSubscriptions()) || [], }, From 2a4c7d53f75765851df433eca86dec06e9419f57 Mon Sep 17 00:00:00 2001 From: Arik Smith Date: Wed, 14 Jun 2023 15:03:26 -0400 Subject: [PATCH 137/251] [#963] separate middleware functions and add comment documentation --- packages/api/src/api.ts | 120 ++---------------------------- packages/api/src/middleware.ts | 132 +++++++++++++++++++++++++++++++++ 2 files changed, 137 insertions(+), 115 deletions(-) create mode 100644 packages/api/src/middleware.ts diff --git a/packages/api/src/api.ts b/packages/api/src/api.ts index a821cb3d6..b618f2317 100644 --- a/packages/api/src/api.ts +++ b/packages/api/src/api.ts @@ -3,122 +3,12 @@ */ import express from 'express' - -import { - App, - createValidFootprintRequest, - createValidRecommendationsRequest, - FootprintEstimatesRawRequest, - RecommendationsRawRequest, - Tags, -} from '@cloud-carbon-footprint/app' - +import { setConfig, CCFConfig } from '@cloud-carbon-footprint/common' import { - setConfig, - CCFConfig, - EstimationRequestValidationError, - Logger, - PartialDataError, - RecommendationsRequestValidationError, -} from '@cloud-carbon-footprint/common' - -const apiLogger = new Logger('api') - -/** - * Returns the raw estimates - * - * Query params: - * start - Required, UTC start date in format YYYY-MM-DD - * end - Required, UTC start date in format YYYY-MM-DD - */ -const FootprintApiMiddleware = async function ( - req: express.Request, - res: express.Response, -): Promise { - // Set the request time out to 10 minutes to allow the request enough time to complete. - req.socket.setTimeout(1000 * 60 * 10) - const rawRequest: FootprintEstimatesRawRequest = { - startDate: req.query.start?.toString(), - endDate: req.query.end?.toString(), - ignoreCache: req.query.ignoreCache?.toString(), - groupBy: req.query.groupBy?.toString(), - limit: req.query.limit?.toString(), - skip: req.query.skip?.toString(), - cloudProviders: req.query.cloudProviders as string[], - accounts: req.query.accounts as string[], - services: req.query.services as string[], - regions: req.query.regions as string[], - tags: req.query.tags as Tags, - } - apiLogger.info(`Footprint API request started.`) - if (!rawRequest.groupBy) { - apiLogger.warn('GroupBy parameter not specified, adopting default "day"') - rawRequest.groupBy = 'day' - } - const footprintApp = new App() - try { - const estimationRequest = createValidFootprintRequest(rawRequest) - const estimationResults = await footprintApp.getCostAndEstimates( - estimationRequest, - ) - res.json(estimationResults) - } catch (e) { - apiLogger.error(`Unable to process footprint request.`, e) - if ( - e.constructor.name === - EstimationRequestValidationError.prototype.constructor.name - ) { - res.status(400).send(e.message) - } else if ( - e.constructor.name === PartialDataError.prototype.constructor.name - ) { - res.status(416).send(e.message) - } else res.status(500).send('Internal Server Error') - } -} - -const EmissionsApiMiddleware = async function ( - req: express.Request, - res: express.Response, -): Promise { - apiLogger.info(`Regions emissions factors API request started`) - const footprintApp = new App() - try { - const emissionsResults = await footprintApp.getEmissionsFactors() - res.json(emissionsResults) - } catch (e) { - apiLogger.error(`Unable to process regions emissions factors request.`, e) - res.status(500).send('Internal Server Error') - } -} - -const RecommendationsApiMiddleware = async function ( - req: express.Request, - res: express.Response, -): Promise { - const rawRequest: RecommendationsRawRequest = { - awsRecommendationTarget: req.query.awsRecommendationTarget?.toString(), - } - apiLogger.info(`Recommendations API request started`) - const footprintApp = new App() - try { - const estimationRequest = createValidRecommendationsRequest(rawRequest) - const recommendations = await footprintApp.getRecommendations( - estimationRequest, - ) - res.json(recommendations) - } catch (e) { - apiLogger.error(`Unable to process recommendations request.`, e) - if ( - e.constructor.name === - RecommendationsRequestValidationError.prototype.constructor.name - ) { - res.status(400).send(e.message) - } else { - res.status(500).send('Internal Server Error') - } - } -} + FootprintApiMiddleware, + EmissionsApiMiddleware, + RecommendationsApiMiddleware, +} from './middleware' export const createRouter = (config?: CCFConfig) => { setConfig(config) diff --git a/packages/api/src/middleware.ts b/packages/api/src/middleware.ts new file mode 100644 index 000000000..29080bc49 --- /dev/null +++ b/packages/api/src/middleware.ts @@ -0,0 +1,132 @@ +/* + * © 2023 Thoughtworks, Inc. + */ + +import express from 'express' + +import { + App, + createValidFootprintRequest, + createValidRecommendationsRequest, + FootprintEstimatesRawRequest, + RecommendationsRawRequest, + Tags, +} from '@cloud-carbon-footprint/app' + +import { + EstimationRequestValidationError, + Logger, + PartialDataError, + RecommendationsRequestValidationError, +} from '@cloud-carbon-footprint/common' + +const apiLogger = new Logger('api') + +/** + * Handles the fetching and calculations of cloud footprint estimates for a given date range. + * + * @async + * @param {express.Request} req - The Express request object containing the request parameters. + * @returns A response object with the calculated raw footprint estimates. + */ +export const FootprintApiMiddleware = async function ( + req: express.Request, + res: express.Response, +): Promise { + // Set the request time out to 10 minutes to allow the request enough time to complete. + req.socket.setTimeout(1000 * 60 * 10) + const rawRequest: FootprintEstimatesRawRequest = { + startDate: req.query.start?.toString(), + endDate: req.query.end?.toString(), + ignoreCache: req.query.ignoreCache?.toString(), + groupBy: req.query.groupBy?.toString(), + limit: req.query.limit?.toString(), + skip: req.query.skip?.toString(), + cloudProviders: req.query.cloudProviders as string[], + accounts: req.query.accounts as string[], + services: req.query.services as string[], + regions: req.query.regions as string[], + tags: req.query.tags as Tags, + } + apiLogger.info(`Footprint API request started.`) + if (!rawRequest.groupBy) { + apiLogger.warn('GroupBy parameter not specified, adopting default "day"') + rawRequest.groupBy = 'day' + } + const footprintApp = new App() + try { + const estimationRequest = createValidFootprintRequest(rawRequest) + const estimationResults = await footprintApp.getCostAndEstimates( + estimationRequest, + ) + res.json(estimationResults) + } catch (e) { + apiLogger.error(`Unable to process footprint request.`, e) + if ( + e.constructor.name === + EstimationRequestValidationError.prototype.constructor.name + ) { + res.status(400).send(e.message) + } else if ( + e.constructor.name === PartialDataError.prototype.constructor.name + ) { + res.status(416).send(e.message) + } else res.status(500).send('Internal Server Error') + } +} + +/** + * Handles the fetching of emissions factors for different regions. + * + * @async + * @returns A response object with the mapped emissions factors for each supported cloud provider region. + */ +export const EmissionsApiMiddleware = async function ( + _req: express.Request, + res: express.Response, +): Promise { + apiLogger.info(`Regions emissions factors API request started`) + const footprintApp = new App() + try { + const emissionsResults = await footprintApp.getEmissionsFactors() + res.json(emissionsResults) + } catch (e) { + apiLogger.error(`Unable to process regions emissions factors request.`, e) + res.status(500).send('Internal Server Error') + } +} + +/** + * Handles the fetching of cloud footprint recommendations for a given AWS recommendation target. + * + * @async + * @param {express.Request} req - The Express request object containing the request parameters. + * @returns A response object with the fetched recommendations and their calculated carbon and energy savings. + */ +export const RecommendationsApiMiddleware = async function ( + req: express.Request, + res: express.Response, +): Promise { + const rawRequest: RecommendationsRawRequest = { + awsRecommendationTarget: req.query.awsRecommendationTarget?.toString(), + } + apiLogger.info(`Recommendations API request started`) + const footprintApp = new App() + try { + const estimationRequest = createValidRecommendationsRequest(rawRequest) + const recommendations = await footprintApp.getRecommendations( + estimationRequest, + ) + res.json(recommendations) + } catch (e) { + apiLogger.error(`Unable to process recommendations request.`, e) + if ( + e.constructor.name === + RecommendationsRequestValidationError.prototype.constructor.name + ) { + res.status(400).send(e.message) + } else { + res.status(500).send('Internal Server Error') + } + } +} From 5ac6ddf9bb895b6795ed048875e5555e6b084ff2 Mon Sep 17 00:00:00 2001 From: Arik Smith Date: Wed, 14 Jun 2023 16:17:46 -0400 Subject: [PATCH 138/251] [#963] add test directory and tests for swagger --- packages/api/src/{ => __tests__}/api.test.ts | 2 +- packages/api/src/__tests__/swagger.test.ts | 39 ++++++++++++++++++++ packages/api/src/swagger.ts | 2 +- 3 files changed, 41 insertions(+), 2 deletions(-) rename packages/api/src/{ => __tests__}/api.test.ts (99%) create mode 100644 packages/api/src/__tests__/swagger.test.ts diff --git a/packages/api/src/api.test.ts b/packages/api/src/__tests__/api.test.ts similarity index 99% rename from packages/api/src/api.test.ts rename to packages/api/src/__tests__/api.test.ts index 40ebd3ac5..474bb7bd7 100644 --- a/packages/api/src/api.test.ts +++ b/packages/api/src/__tests__/api.test.ts @@ -11,7 +11,7 @@ import { RecommendationResult, } from '@cloud-carbon-footprint/common' -import { createRouter } from './api' +import { createRouter } from '../api' const mockGetCostAndEstimates = jest.fn() const mockGetEmissionsFactors = jest.fn() diff --git a/packages/api/src/__tests__/swagger.test.ts b/packages/api/src/__tests__/swagger.test.ts new file mode 100644 index 000000000..bc1779f0d --- /dev/null +++ b/packages/api/src/__tests__/swagger.test.ts @@ -0,0 +1,39 @@ +import { Logger } from '@cloud-carbon-footprint/common' +import request from 'supertest' +import express from 'express' +import swaggerDocs from '../swagger' + +describe('swaggerDocs', () => { + const server = express() + const port = 3000 + + beforeAll(() => { + swaggerDocs(server, port) + }) + + it('should return the Swagger UI page', async () => { + const res = await request(server).get(encodeURI('/docs')).redirects(1) + expect(res.status).toEqual(200) + expect(res.text).toContain('Swagger UI') + }) + + it('should return the Swagger JSON file', async () => { + const res = await request(server).get('/docs.json') + expect(res.status).toEqual(200) + expect(res.body).toHaveProperty('openapi') + expect(res.body.openapi).toEqual('3.0.0') + expect(res.body).toHaveProperty('info') + expect(res.body.info).toHaveProperty('title') + expect(res.body.info.title).toEqual('CCF API Docs') + expect(res.body.info).toHaveProperty('version') + expect(res.body.info.version).toEqual('1.6.0') + }) + + it('should log the availability of the docs', () => { + const spy = jest.spyOn(Logger.prototype, 'info') + swaggerDocs(server, port) + expect(spy).toHaveBeenCalledWith( + `Documentation available at http://localhost:${port}/docs`, + ) + }) +}) diff --git a/packages/api/src/swagger.ts b/packages/api/src/swagger.ts index 01a8c5e94..e923fb31a 100644 --- a/packages/api/src/swagger.ts +++ b/packages/api/src/swagger.ts @@ -30,7 +30,7 @@ function swaggerDocs(app: Express, port: number) { res.send(swaggerSpec) }) - serverLogger.info(`Docs available at http://localhost:${port}/docs`) + serverLogger.info(`Documentation available at http://localhost:${port}/docs`) } export default swaggerDocs From f44edc2e5e44de96aacc6fcf7cdf89ba8bb2976c Mon Sep 17 00:00:00 2001 From: Arik Smith Date: Wed, 14 Jun 2023 19:12:06 -0400 Subject: [PATCH 139/251] [#963] removes response specs from common and finishes documentation --- packages/api/src/__tests__/swagger.test.ts | 2 +- packages/api/src/api.ts | 54 ++++++++----- packages/api/src/middleware.ts | 4 +- packages/api/src/server.ts | 4 +- packages/api/src/{ => utils}/auth.ts | 0 packages/api/src/utils/schemas.yaml | 85 +++++++++++++++++++++ packages/api/src/{ => utils}/swagger.ts | 2 +- packages/common/src/EmissionRatioResult.ts | 15 ---- packages/common/src/EstimationResult.ts | 41 ---------- packages/common/src/RecommendationResult.ts | 34 --------- 10 files changed, 126 insertions(+), 115 deletions(-) rename packages/api/src/{ => utils}/auth.ts (100%) create mode 100644 packages/api/src/utils/schemas.yaml rename packages/api/src/{ => utils}/swagger.ts (94%) diff --git a/packages/api/src/__tests__/swagger.test.ts b/packages/api/src/__tests__/swagger.test.ts index bc1779f0d..228d8a4a6 100644 --- a/packages/api/src/__tests__/swagger.test.ts +++ b/packages/api/src/__tests__/swagger.test.ts @@ -1,7 +1,7 @@ import { Logger } from '@cloud-carbon-footprint/common' import request from 'supertest' import express from 'express' -import swaggerDocs from '../swagger' +import swaggerDocs from '../utils/swagger' describe('swaggerDocs', () => { const server = express() diff --git a/packages/api/src/api.ts b/packages/api/src/api.ts index b618f2317..ac69ac72e 100644 --- a/packages/api/src/api.ts +++ b/packages/api/src/api.ts @@ -14,13 +14,16 @@ export const createRouter = (config?: CCFConfig) => { setConfig(config) const router = express.Router() + /** * @openapi * /api/footprint: * get: * tags: * - Footprint - * summary: Get the footprint for the date range + * summary: Gets calculated energy and carbon estimates for a given date range + * produces: + * - application/json * parameters: * - name: start * in: query @@ -51,14 +54,14 @@ export const createRouter = (config?: CCFConfig) => { * schema: * type: number * default: 50000 - * description: The maximum number of estimates to return + * description: The maximum number of estimates to return (MongoDB only, ignoreCache=false) * required: false * - name: skip * in: query * schema: * type: number * default: 0 - * description: The number of estimates to skip over + * description: The maximum number of estimates to skip over (MongoDB only, ignoreCache=false) * required: false * - name: cloudProviders * in: query @@ -66,8 +69,7 @@ export const createRouter = (config?: CCFConfig) => { * type: array * items: * type: string - * default: [] - * description: Can be used to filter estimates + * description: List of Cloud Providers to include in estimates (MongoDB only, Filter) * required: false * - name: accounts * in: query @@ -75,8 +77,7 @@ export const createRouter = (config?: CCFConfig) => { * type: array * items: * type: string - * default: [] - * description: Can be used to filter estimates + * description: List of accounts to include in estimates (MongoDB only, Filter) * required: false * - name: services * in: query @@ -84,8 +85,7 @@ export const createRouter = (config?: CCFConfig) => { * type: array * items: * type: string - * default: [] - * description: Can be used to filter estimates + * description: List of services to include in estimates (MongoDB only, Filter) * required: false * - name: regions * in: query @@ -93,8 +93,7 @@ export const createRouter = (config?: CCFConfig) => { * type: array * items: * type: string - * default: [] - * description: Can be used to filter estimates + * description: List of regions to include in estimates (MongoDB only, Filter) * required: false * - name: tags * in: query @@ -102,8 +101,7 @@ export const createRouter = (config?: CCFConfig) => { * type: object * additionalProperties: * type: string - * default: {} - * description: Can be used to filter estimates + * description: List of resource tags to include in estimates (MongoDB only, Filter) * required: false * responses: * 200: @@ -111,9 +109,15 @@ export const createRouter = (config?: CCFConfig) => { * content: * application/json: * schema: - * $ref: '#/components/schemas/FootprintResponse' + * type: array + * items: + * $ref: '#/components/schemas/FootprintResponse' * 400: * description: Bad request + * 416: + * description: Partial Data Error + * 500: + * description: Internal Server Error */ router.get('/footprint', FootprintApiMiddleware) @@ -123,14 +127,16 @@ export const createRouter = (config?: CCFConfig) => { * get: * tags: * - Emissions Factors - * description: Gives you back the emissions factors for all regions? + * description: Gets the carbon intensity (co2e/kWh) of all cloud provider regions * responses: * 200: * description: Success * content: * application/json: * schema: - * $ref: '#/components/schemas/EmissionResponse' + * type: array + * items: + * $ref: '#/components/schemas/EmissionResponse' */ router.get('/regions/emissions-factors', EmissionsApiMiddleware) @@ -140,14 +146,24 @@ export const createRouter = (config?: CCFConfig) => { * get: * tags: * - Recommendations - * description: Gives you back recommendations to decrease your cloud carbon footprint + * description: Gets recommendations from cloud providers and their estimated carbon and energy impact + * parameters: + * - name: awsRecommendationTarget + * in: query + * description: Defines whether targeted AWS recommendations should be within the same family + * schema: + * type: string + * enum: [SAME_INSTANCE_FAMILY, CROSS_INSTANCE_FAMILY] + * required: true * responses: * 200: * description: Success * content: * application/json: * schema: - * $ref: '#/components/schemas/RecommendationsResponse' + * type: array + * items: + * $ref: '#/components/schemas/RecommendationsResponse' */ router.get('/recommendations', RecommendationsApiMiddleware) @@ -160,7 +176,7 @@ export const createRouter = (config?: CCFConfig) => { * description: Responds if the app is up and running * responses: * 200: - * description: App is up and running + * description: Responds "OK" if app is up and running */ router.get('/healthz', (req: express.Request, res: express.Response) => { res.status(200).send('OK') diff --git a/packages/api/src/middleware.ts b/packages/api/src/middleware.ts index 29080bc49..7541fb1c8 100644 --- a/packages/api/src/middleware.ts +++ b/packages/api/src/middleware.ts @@ -76,7 +76,7 @@ export const FootprintApiMiddleware = async function ( } /** - * Handles the fetching of emissions factors for different regions. + * Handles the fetching of emissions factors for all regions. * * @async * @returns A response object with the mapped emissions factors for each supported cloud provider region. @@ -97,7 +97,7 @@ export const EmissionsApiMiddleware = async function ( } /** - * Handles the fetching of cloud footprint recommendations for a given AWS recommendation target. + * Handles the fetching of cost saving recommendations along with their calculated carbon and energy savings. * * @async * @param {express.Request} req - The Express request object containing the request parameters. diff --git a/packages/api/src/server.ts b/packages/api/src/server.ts index f2c77d3ea..a9710d85e 100644 --- a/packages/api/src/server.ts +++ b/packages/api/src/server.ts @@ -11,9 +11,9 @@ import helmet from 'helmet' import cors, { CorsOptions } from 'cors' import { createRouter } from './api' -import auth from './auth' +import auth from './utils/auth' import { Logger } from '@cloud-carbon-footprint/common' -import swaggerDocs from './swagger' +import swaggerDocs from './utils/swagger' const port = process.env.PORT || 4000 const httpApp = express() diff --git a/packages/api/src/auth.ts b/packages/api/src/utils/auth.ts similarity index 100% rename from packages/api/src/auth.ts rename to packages/api/src/utils/auth.ts diff --git a/packages/api/src/utils/schemas.yaml b/packages/api/src/utils/schemas.yaml new file mode 100644 index 000000000..2bfbbc3e5 --- /dev/null +++ b/packages/api/src/utils/schemas.yaml @@ -0,0 +1,85 @@ +# Schemas used for Swagger Documentation (follows OpenAPI Specs) +# These represent types that are defined in the Common package and will need to be updated when its counterpart is changed + +components: + schemas: + FootprintResponse: + type: object + properties: + timestamp: + type: string + serviceEstimates: + type: array + items: + type: object + properties: + cloudProvider: + type: string + enum: [aws, gcp, azure] + kilowattHours: + type: number + co2e: + type: number + cost: + type: number + usesAverageCPUConstant?: + type: boolean + accountId: + type: string + accountName: + type: string + serviceName: + type: string + region: + type: string + tags: + schema: + type: array + items: + type: object + additionalProperties: + type: string + periodStartDate: + type: string + periodEndDate: + type: string + groupBy: + type: string + EmissionResponse: + type: object + properties: + cloudProvider: + type: string + region: + type: string + mtPerKwHour: + type: number + description: metric ton co2e per kilowatt-hour + RecommendationsResponse: + type: object + properties: + cloudProvider: + type: string + enum: [aws, gcp, azure] + accountId: + type: string + accountName: + type: string + region: + type: string + recommendationType: + type: string + recommendationDetail?: + type: string + resourceId?: + type: string + instanceName?: + type: string + kilowattHourSavings: + type: number + costSavings: + type: number + co2eSavings: + type: number + recommendationOptions?: + type: object diff --git a/packages/api/src/swagger.ts b/packages/api/src/utils/swagger.ts similarity index 94% rename from packages/api/src/swagger.ts rename to packages/api/src/utils/swagger.ts index e923fb31a..cbac45ba7 100644 --- a/packages/api/src/swagger.ts +++ b/packages/api/src/utils/swagger.ts @@ -15,7 +15,7 @@ const options: swaggerJsdoc.Options = { version, }, }, - apis: ['./src/api.ts', '../common/src/*.ts'], + apis: ['./src/api.ts', './src/utils/schemas.yaml'], } const swaggerSpec = swaggerJsdoc(options) diff --git a/packages/common/src/EmissionRatioResult.ts b/packages/common/src/EmissionRatioResult.ts index 29340a904..7f0def962 100644 --- a/packages/common/src/EmissionRatioResult.ts +++ b/packages/common/src/EmissionRatioResult.ts @@ -2,21 +2,6 @@ * © 2021 Thoughtworks, Inc. */ -/** - * @openapi - * components: - * schemas: - * EmissionResponse: - * type: object - * properties: - * cloudProvider: - * type: string - * region: - * type: string - * mtPerKwHour: - * type: number - * description: metric ton co2e per kwHour - */ export type EmissionRatioResult = { cloudProvider: string region: string diff --git a/packages/common/src/EstimationResult.ts b/packages/common/src/EstimationResult.ts index 6bf72fbcc..72cd676a1 100644 --- a/packages/common/src/EstimationResult.ts +++ b/packages/common/src/EstimationResult.ts @@ -6,47 +6,6 @@ import { reduceBy } from 'ramda' import { GroupBy } from './Config' import { getPeriodEndDate } from './helpers' -/** - * @openapi - * components: - * schemas: - * FootprintResponse: - * type: object - * properties: - * timestamp: - * type: string - * serviceEstimates: - * type: array - * items: - * type: object - * properties: - * cloudProvider: - * type: string - * kilowattHours: - * type: number - * co2e: - * type: number - * cost: - * type: number - * usesAverageCPUConstant?: - * type: boolean - * accountId: - * type: string - * accountName: - * type: string - * serviceName: - * type: string - * region: - * type: string - * tags: - * type: array - * periodStartDate: - * type: string - * periodEndDate: - * type: string - * groupBy: - * type: string - */ export interface EstimationResult { readonly timestamp: Date readonly serviceEstimates: ServiceData[] diff --git a/packages/common/src/RecommendationResult.ts b/packages/common/src/RecommendationResult.ts index ec546be37..523dc0cdc 100644 --- a/packages/common/src/RecommendationResult.ts +++ b/packages/common/src/RecommendationResult.ts @@ -2,40 +2,6 @@ * © 2021 Thoughtworks, Inc. */ -/** - * @openapi - * components: - * schemas: - * RecommendationsResponse: - * type: object - * properties: - * cloudProvider: - * type: string - * description: aws | gcp | azure - * accountId: - * type: string - * accountName: - * type: string - * region: - * type: string - * recommendationType: - * type: string - * recommendationDetail?: - * type: string - * resourceId?: - * type: string - * instanceName?: - * type: string - * kilowattHourSavings: - * type: number - * costSavings: - * type: number - * co2eSavings: - * type: number - * recommendationOptions?: - * type: object - * description: Either EC2, EBS, or Lambda recommendation - */ export interface RecommendationResult { readonly cloudProvider: string readonly accountId: string From 045ef4190079dfde5721bdb1ceeeed7375df3cea Mon Sep 17 00:00:00 2001 From: Arik Smith Date: Wed, 14 Jun 2023 19:25:50 -0400 Subject: [PATCH 140/251] changeset: Adds OpenAPI spec documentation and Swagger portal --- .changeset/unlucky-hotels-tease.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/unlucky-hotels-tease.md diff --git a/.changeset/unlucky-hotels-tease.md b/.changeset/unlucky-hotels-tease.md new file mode 100644 index 000000000..5f56b94d7 --- /dev/null +++ b/.changeset/unlucky-hotels-tease.md @@ -0,0 +1,5 @@ +--- +'@cloud-carbon-footprint/api': minor +--- + +Adds OpenAPI spec documentation and Swagger portal From af4c4128b1f926f75b5675f8dee36f2e733002d0 Mon Sep 17 00:00:00 2001 From: Kevin Date: Fri, 26 May 2023 11:27:38 +0200 Subject: [PATCH 141/251] fix: recommendations now only show values from last 30 days --- .../client/src/utils/hooks/RecommendationsServiceHook.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/packages/client/src/utils/hooks/RecommendationsServiceHook.ts b/packages/client/src/utils/hooks/RecommendationsServiceHook.ts index 5aea110e1..0221447d7 100644 --- a/packages/client/src/utils/hooks/RecommendationsServiceHook.ts +++ b/packages/client/src/utils/hooks/RecommendationsServiceHook.ts @@ -33,6 +33,15 @@ const useRemoteRecommendationsService = ( } setError(null) + const currentDate = new Date() + params.footprint.data = params.footprint.data.filter((element) => { + const elementDate = new Date(element.timestamp) + const diffInDays = Math.ceil( + (currentDate.getTime() - elementDate.getTime()) / (1000 * 60 * 60 * 24), + ) + return diffInDays <= 30 + }) + try { const res = params.awsRecommendationTarget ? await axios.get(`${params.baseUrl}/recommendations`, { From 64b6cbc0cab1457926469c51e5b77ea98572e1a6 Mon Sep 17 00:00:00 2001 From: Kevin Date: Fri, 26 May 2023 11:29:52 +0200 Subject: [PATCH 142/251] fix: comma were not showing in recommendation --- packages/client/src/utils/helpers/transformData.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/client/src/utils/helpers/transformData.ts b/packages/client/src/utils/helpers/transformData.ts index beb6f1521..2aac35f8c 100644 --- a/packages/client/src/utils/helpers/transformData.ts +++ b/packages/client/src/utils/helpers/transformData.ts @@ -277,9 +277,8 @@ const useFilterDataFromRecommendations = ( function tableFormatNearZero(rawValue: number): string { const formattedValue = rawValue .toLocaleString(undefined, { - maximumFractionDigits: 3, + maximumFractionDigits: 2, }) - .replace(',', '') return formattedValue === '0' && rawValue > 0 ? '< 0.001' : formattedValue } From db18d4f597a6d677a30203a397fa5754787eb084 Mon Sep 17 00:00:00 2001 From: Kevin Monvoisin <121902923+kevinmonvoisin-reply@users.noreply.github.com> Date: Fri, 26 May 2023 11:49:14 +0200 Subject: [PATCH 143/251] fix: yarn lint --- packages/client/src/utils/helpers/transformData.ts | 2 +- packages/client/src/utils/hooks/RecommendationsServiceHook.ts | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/client/src/utils/helpers/transformData.ts b/packages/client/src/utils/helpers/transformData.ts index 2aac35f8c..b2afb5b03 100644 --- a/packages/client/src/utils/helpers/transformData.ts +++ b/packages/client/src/utils/helpers/transformData.ts @@ -277,7 +277,7 @@ const useFilterDataFromRecommendations = ( function tableFormatNearZero(rawValue: number): string { const formattedValue = rawValue .toLocaleString(undefined, { - maximumFractionDigits: 2, + maximumFractionDigits: 3, }) return formattedValue === '0' && rawValue > 0 ? '< 0.001' : formattedValue } diff --git a/packages/client/src/utils/hooks/RecommendationsServiceHook.ts b/packages/client/src/utils/hooks/RecommendationsServiceHook.ts index 0221447d7..c3dc520cb 100644 --- a/packages/client/src/utils/hooks/RecommendationsServiceHook.ts +++ b/packages/client/src/utils/hooks/RecommendationsServiceHook.ts @@ -37,7 +37,8 @@ const useRemoteRecommendationsService = ( params.footprint.data = params.footprint.data.filter((element) => { const elementDate = new Date(element.timestamp) const diffInDays = Math.ceil( - (currentDate.getTime() - elementDate.getTime()) / (1000 * 60 * 60 * 24), + (currentDate.getTime() - elementDate.getTime()) / + (1000 * 60 * 60 * 24), ) return diffInDays <= 30 }) From 2640851a8a95873c050ff1d1146410c227076b7e Mon Sep 17 00:00:00 2001 From: Kevin Monvoisin Date: Tue, 13 Jun 2023 15:01:08 +0200 Subject: [PATCH 144/251] chore(client): yarn lint --- packages/client/src/utils/helpers/transformData.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/client/src/utils/helpers/transformData.ts b/packages/client/src/utils/helpers/transformData.ts index b2afb5b03..3fa52acdf 100644 --- a/packages/client/src/utils/helpers/transformData.ts +++ b/packages/client/src/utils/helpers/transformData.ts @@ -275,10 +275,9 @@ const useFilterDataFromRecommendations = ( * @param rawValue Raw numeric value to format */ function tableFormatNearZero(rawValue: number): string { - const formattedValue = rawValue - .toLocaleString(undefined, { - maximumFractionDigits: 3, - }) + const formattedValue = rawValue.toLocaleString(undefined, { + maximumFractionDigits: 3, + }) return formattedValue === '0' && rawValue > 0 ? '< 0.001' : formattedValue } From b572cbfd7fb50165bcdd61d40f212935ade4d360 Mon Sep 17 00:00:00 2001 From: Kevin Monvoisin Date: Tue, 13 Jun 2023 15:09:54 +0200 Subject: [PATCH 145/251] fix: recommendations now show last 30 days data --- .../src/utils/hooks/RecommendationsServiceHook.ts | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/packages/client/src/utils/hooks/RecommendationsServiceHook.ts b/packages/client/src/utils/hooks/RecommendationsServiceHook.ts index c3dc520cb..a4a447293 100644 --- a/packages/client/src/utils/hooks/RecommendationsServiceHook.ts +++ b/packages/client/src/utils/hooks/RecommendationsServiceHook.ts @@ -8,7 +8,7 @@ import { RecommendationResult } from '@cloud-carbon-footprint/common' import { ServiceResult } from '../../Types' import { useAxiosErrorHandling } from '../../layout/ErrorPage' import { FootprintData } from './FootprintDataHook' - +import { sliceFootprintDataByLastMonth } from '../helpers/handleDates' export interface UseRemoteRecommendationServiceParams { baseUrl: string | null onApiError?: (e: Error) => void @@ -33,15 +33,9 @@ const useRemoteRecommendationsService = ( } setError(null) - const currentDate = new Date() - params.footprint.data = params.footprint.data.filter((element) => { - const elementDate = new Date(element.timestamp) - const diffInDays = Math.ceil( - (currentDate.getTime() - elementDate.getTime()) / - (1000 * 60 * 60 * 24), - ) - return diffInDays <= 30 - }) + params.footprint.data = sliceFootprintDataByLastMonth( + params.footprint.data, + ) try { const res = params.awsRecommendationTarget From 81bfc51c616042d6e25f7a40b1514a33bdc81c57 Mon Sep 17 00:00:00 2001 From: Arik Smith Date: Mon, 19 Jun 2023 16:33:30 -0400 Subject: [PATCH 146/251] simplifies emissions data slicing for recommendations page --- .../src/pages/RecommendationsPage/RecommendationsPage.tsx | 2 +- .../RecommendationsTable/RecommendationsTable.test.tsx | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/client/src/pages/RecommendationsPage/RecommendationsPage.tsx b/packages/client/src/pages/RecommendationsPage/RecommendationsPage.tsx index 1dfad0e24..0e8ea60c8 100644 --- a/packages/client/src/pages/RecommendationsPage/RecommendationsPage.tsx +++ b/packages/client/src/pages/RecommendationsPage/RecommendationsPage.tsx @@ -60,7 +60,7 @@ const RecommendationsPage = ({
{ const firstRow = actualRowData[0] - expect(firstRow[firstRow.length - 1].innerHTML).toBe('2560') + expect(firstRow[firstRow.length - 1].innerHTML).toBe('2,560') const table = within(getByRole('grid')) @@ -395,7 +395,7 @@ describe('Recommendations Table', () => { ['us-west-1', true, 1, Co2eUnit.MetricTonnes], ['Modify', true, 1, Co2eUnit.MetricTonnes], [2.539, true, 1, Co2eUnit.MetricTonnes], - [2539, true, 1, Co2eUnit.Kilograms], + ['2,539', true, 1, Co2eUnit.Kilograms], ['pizza', undefined, 0, Co2eUnit.MetricTonnes], [6.2, undefined, 0, Co2eUnit.MetricTonnes], ] From fb83ef5fd80178e4cd287ccf4d3482017086af0d Mon Sep 17 00:00:00 2001 From: Arik Smith Date: Mon, 19 Jun 2023 16:36:57 -0400 Subject: [PATCH 147/251] updates tests and recs service hook with new emissions data --- packages/client/src/utils/helpers/transformData.test.ts | 2 +- .../client/src/utils/hooks/RecommendationsServiceHook.ts | 5 ----- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/packages/client/src/utils/helpers/transformData.test.ts b/packages/client/src/utils/helpers/transformData.test.ts index 0f0fc265f..ba603904b 100644 --- a/packages/client/src/utils/helpers/transformData.test.ts +++ b/packages/client/src/utils/helpers/transformData.test.ts @@ -418,7 +418,7 @@ describe('tableFormatRawCo2e', () => { [0.00099, Co2eUnit.MetricTonnes, '0.001'], [0.00099, Co2eUnit.Kilograms, '0.99'], [1, Co2eUnit.MetricTonnes, '1'], - [1, Co2eUnit.Kilograms, '1000'], + [1, Co2eUnit.Kilograms, '1,000'], ] each(a).it( ' formats Co2e properly', diff --git a/packages/client/src/utils/hooks/RecommendationsServiceHook.ts b/packages/client/src/utils/hooks/RecommendationsServiceHook.ts index a4a447293..433640b2a 100644 --- a/packages/client/src/utils/hooks/RecommendationsServiceHook.ts +++ b/packages/client/src/utils/hooks/RecommendationsServiceHook.ts @@ -8,7 +8,6 @@ import { RecommendationResult } from '@cloud-carbon-footprint/common' import { ServiceResult } from '../../Types' import { useAxiosErrorHandling } from '../../layout/ErrorPage' import { FootprintData } from './FootprintDataHook' -import { sliceFootprintDataByLastMonth } from '../helpers/handleDates' export interface UseRemoteRecommendationServiceParams { baseUrl: string | null onApiError?: (e: Error) => void @@ -33,10 +32,6 @@ const useRemoteRecommendationsService = ( } setError(null) - params.footprint.data = sliceFootprintDataByLastMonth( - params.footprint.data, - ) - try { const res = params.awsRecommendationTarget ? await axios.get(`${params.baseUrl}/recommendations`, { From 3d7a21ff08e4ce26be0aef3e0d56f4df61e06893 Mon Sep 17 00:00:00 2001 From: Arik Smith Date: Mon, 19 Jun 2023 16:40:08 -0400 Subject: [PATCH 148/251] changeset: Improves recommendations forecast accuracy, and fixes cost formatting errors for local currencies --- .changeset/fuzzy-bulldogs-sing.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/fuzzy-bulldogs-sing.md diff --git a/.changeset/fuzzy-bulldogs-sing.md b/.changeset/fuzzy-bulldogs-sing.md new file mode 100644 index 000000000..159184ba9 --- /dev/null +++ b/.changeset/fuzzy-bulldogs-sing.md @@ -0,0 +1,5 @@ +--- +'@cloud-carbon-footprint/client': patch +--- + +Improves recommendations forecast accuracy, and fixes cost formatting errors for local currencies From 6699a64a0a2000d906f0cccc19a013c7b166771e Mon Sep 17 00:00:00 2001 From: Arik Smith Date: Wed, 21 Jun 2023 16:33:01 -0400 Subject: [PATCH 149/251] removes unnecessary json module config and swagger import --- packages/api/src/utils/swagger.ts | 1 - packages/api/tsconfig.json | 1 - 2 files changed, 2 deletions(-) diff --git a/packages/api/src/utils/swagger.ts b/packages/api/src/utils/swagger.ts index cbac45ba7..5fd4eb409 100644 --- a/packages/api/src/utils/swagger.ts +++ b/packages/api/src/utils/swagger.ts @@ -1,7 +1,6 @@ import { Express, Request, Response } from 'express' import swaggerJsdoc from 'swagger-jsdoc' import swaggerUi from 'swagger-ui-express' -// import {version} from '../package.json' import { Logger } from '@cloud-carbon-footprint/common' const serverLogger = new Logger('server') diff --git a/packages/api/tsconfig.json b/packages/api/tsconfig.json index ac8cce504..dd24e23d0 100644 --- a/packages/api/tsconfig.json +++ b/packages/api/tsconfig.json @@ -5,7 +5,6 @@ "baseUrl": ".", "outDir": "./dist", "skipLibCheck": true, - "resolveJsonModule": true }, "include": ["./src"], } From e8e5ba81e6a4cd1f5837e26f67ce2ce5705f4fca Mon Sep 17 00:00:00 2001 From: Arik Smith Date: Wed, 21 Jun 2023 16:36:12 -0400 Subject: [PATCH 150/251] add basic test for auth stub --- packages/api/src/__tests__/auth.test.ts | 26 +++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 packages/api/src/__tests__/auth.test.ts diff --git a/packages/api/src/__tests__/auth.test.ts b/packages/api/src/__tests__/auth.test.ts new file mode 100644 index 000000000..1e186580a --- /dev/null +++ b/packages/api/src/__tests__/auth.test.ts @@ -0,0 +1,26 @@ +/* + * © 2021 Thoughtworks, Inc. + */ + +import request from 'supertest' +import express from 'express' + +import authMiddleware from '../utils/auth' + +describe('authMiddleware', () => { + let app: express.Application + + beforeEach(() => { + app = express() + app.use(authMiddleware) + app.get('/test', (_req, res) => { + res.send('Hello World!') + }) + }) + + it('should call next if authentication is successful', async () => { + const response = await request(app).get('/test') + expect(response.status).toBe(200) + expect(response.text).toBe('Hello World!') + }) +}) From e1708e330840daae29edb136f0284908b88dd23a Mon Sep 17 00:00:00 2001 From: Arik Smith Date: Wed, 21 Jun 2023 18:16:45 -0400 Subject: [PATCH 151/251] [#963] updates create-app template --- .../default-app/packages/api/package.json.hbs | 4 +- .../default-app/packages/api/src/api.ts | 280 +++++++++++------- .../packages/api/src/middleware.ts | 132 +++++++++ .../default-app/packages/api/src/server.ts | 12 +- .../packages/api/src/utils/auth.ts | 23 ++ .../packages/api/src/utils/schemas.yaml | 85 ++++++ .../packages/api/src/utils/swagger.ts | 35 +++ 7 files changed, 450 insertions(+), 121 deletions(-) create mode 100644 packages/create-app/templates/default-app/packages/api/src/middleware.ts create mode 100644 packages/create-app/templates/default-app/packages/api/src/utils/auth.ts create mode 100644 packages/create-app/templates/default-app/packages/api/src/utils/schemas.yaml create mode 100644 packages/create-app/templates/default-app/packages/api/src/utils/swagger.ts diff --git a/packages/create-app/templates/default-app/packages/api/package.json.hbs b/packages/create-app/templates/default-app/packages/api/package.json.hbs index 25c7f9665..9f6b96b08 100644 --- a/packages/create-app/templates/default-app/packages/api/package.json.hbs +++ b/packages/create-app/templates/default-app/packages/api/package.json.hbs @@ -81,7 +81,9 @@ "cors": "^2.8.5", "express": "^4.17.1", "helmet": "^5.0.1", - "module-alias": "^2.2.2" + "module-alias": "^2.2.2", + "swagger-jsdoc": "^6.2.8", + "swagger-ui-express": "^4.6.2" }, "lint-staged": { "*.{js,ts}": [ diff --git a/packages/create-app/templates/default-app/packages/api/src/api.ts b/packages/create-app/templates/default-app/packages/api/src/api.ts index b39bc3184..ac69ac72e 100644 --- a/packages/create-app/templates/default-app/packages/api/src/api.ts +++ b/packages/create-app/templates/default-app/packages/api/src/api.ts @@ -3,131 +3,181 @@ */ import express from 'express' - -import { - App, - createValidFootprintRequest, - createValidRecommendationsRequest, - FootprintEstimatesRawRequest, - RecommendationsRawRequest, - Tags, -} from '@cloud-carbon-footprint/app' - +import { setConfig, CCFConfig } from '@cloud-carbon-footprint/common' import { - setConfig, - CCFConfig, - EstimationRequestValidationError, - Logger, - PartialDataError, - RecommendationsRequestValidationError, -} from '@cloud-carbon-footprint/common' - -const apiLogger = new Logger('API') - -/** - * Returns the raw estimates - * - * Query params: - * start - Required, UTC start date in format YYYY-MM-DD - * end - Required, UTC start date in format YYYY-MM-DD - */ -const FootprintApiMiddleware = async function ( - req: express.Request, - res: express.Response, -): Promise { - // Set the request time out to 10 minutes to allow the request enough time to complete. - req.socket.setTimeout(1000 * 60 * 10) - const rawRequest: FootprintEstimatesRawRequest = { - startDate: req.query.start?.toString(), - endDate: req.query.end?.toString(), - ignoreCache: req.query.ignoreCache?.toString(), - groupBy: req.query.groupBy?.toString(), - limit: req.query.limit?.toString(), - skip: req.query.skip?.toString(), - cloudProviders: req.query.cloudProviders as string[], - accounts: req.query.accounts as string[], - services: req.query.services as string[], - regions: req.query.regions as string[], - tags: req.query.tags as Tags, - } - apiLogger.info(`Footprint API request started.`) - if (!rawRequest.groupBy) { - apiLogger.warn('GroupBy parameter not specified, adopting default "day"') - rawRequest.groupBy = 'day' - } - const footprintApp = new App() - try { - const estimationRequest = createValidFootprintRequest(rawRequest) - const estimationResults = await footprintApp.getCostAndEstimates( - estimationRequest, - ) - res.json(estimationResults) - } catch (e) { - apiLogger.error(`Unable to process footprint request.`, e) - if ( - e.constructor.name === - EstimationRequestValidationError.prototype.constructor.name - ) { - res.status(400).send(e.message) - } else if ( - e.constructor.name === PartialDataError.prototype.constructor.name - ) { - res.status(416).send(e.message) - } else res.status(500).send('Internal Server Error') - } -} - -const EmissionsApiMiddleware = async function ( - req: express.Request, - res: express.Response, -): Promise { - apiLogger.info(`Regions emissions factors API request started`) - const footprintApp = new App() - try { - const emissionsResults = await footprintApp.getEmissionsFactors() - res.json(emissionsResults) - } catch (e) { - apiLogger.error(`Unable to process regions emissions factors request.`, e) - res.status(500).send('Internal Server Error') - } -} - -const RecommendationsApiMiddleware = async function ( - req: express.Request, - res: express.Response, -): Promise { - const rawRequest: RecommendationsRawRequest = { - awsRecommendationTarget: req.query.awsRecommendationTarget?.toString(), - } - apiLogger.info(`Recommendations API request started`) - const footprintApp = new App() - try { - const estimationRequest = createValidRecommendationsRequest(rawRequest) - const recommendations = await footprintApp.getRecommendations( - estimationRequest, - ) - res.json(recommendations) - } catch (e) { - apiLogger.error(`Unable to process recommendations request.`, e) - if ( - e.constructor.name === - RecommendationsRequestValidationError.prototype.constructor.name - ) { - res.status(400).send(e.message) - } else { - res.status(500).send('Internal Server Error') - } - } -} + FootprintApiMiddleware, + EmissionsApiMiddleware, + RecommendationsApiMiddleware, +} from './middleware' export const createRouter = (config?: CCFConfig) => { setConfig(config) const router = express.Router() + /** + * @openapi + * /api/footprint: + * get: + * tags: + * - Footprint + * summary: Gets calculated energy and carbon estimates for a given date range + * produces: + * - application/json + * parameters: + * - name: start + * in: query + * description: The start date for the footprint; e.g. 2022-10-18 + * schema: + * type: string + * required: true + * - name: end + * in: query + * schema: + * type: string + * description: The end date for the footprint + * required: true + * - name: ignoreCache + * in: query + * schema: + * type: boolean + * default: false + * required: false + * - name: groupBy + * in: query + * schema: + * type: string + * default: day + * required: false + * - name: limit + * in: query + * schema: + * type: number + * default: 50000 + * description: The maximum number of estimates to return (MongoDB only, ignoreCache=false) + * required: false + * - name: skip + * in: query + * schema: + * type: number + * default: 0 + * description: The maximum number of estimates to skip over (MongoDB only, ignoreCache=false) + * required: false + * - name: cloudProviders + * in: query + * schema: + * type: array + * items: + * type: string + * description: List of Cloud Providers to include in estimates (MongoDB only, Filter) + * required: false + * - name: accounts + * in: query + * schema: + * type: array + * items: + * type: string + * description: List of accounts to include in estimates (MongoDB only, Filter) + * required: false + * - name: services + * in: query + * schema: + * type: array + * items: + * type: string + * description: List of services to include in estimates (MongoDB only, Filter) + * required: false + * - name: regions + * in: query + * schema: + * type: array + * items: + * type: string + * description: List of regions to include in estimates (MongoDB only, Filter) + * required: false + * - name: tags + * in: query + * schema: + * type: object + * additionalProperties: + * type: string + * description: List of resource tags to include in estimates (MongoDB only, Filter) + * required: false + * responses: + * 200: + * description: Success + * content: + * application/json: + * schema: + * type: array + * items: + * $ref: '#/components/schemas/FootprintResponse' + * 400: + * description: Bad request + * 416: + * description: Partial Data Error + * 500: + * description: Internal Server Error + */ router.get('/footprint', FootprintApiMiddleware) + + /** + * @openapi + * /api/regions/emissions-factors: + * get: + * tags: + * - Emissions Factors + * description: Gets the carbon intensity (co2e/kWh) of all cloud provider regions + * responses: + * 200: + * description: Success + * content: + * application/json: + * schema: + * type: array + * items: + * $ref: '#/components/schemas/EmissionResponse' + */ router.get('/regions/emissions-factors', EmissionsApiMiddleware) + + /** + * @openapi + * /api/recommendations: + * get: + * tags: + * - Recommendations + * description: Gets recommendations from cloud providers and their estimated carbon and energy impact + * parameters: + * - name: awsRecommendationTarget + * in: query + * description: Defines whether targeted AWS recommendations should be within the same family + * schema: + * type: string + * enum: [SAME_INSTANCE_FAMILY, CROSS_INSTANCE_FAMILY] + * required: true + * responses: + * 200: + * description: Success + * content: + * application/json: + * schema: + * type: array + * items: + * $ref: '#/components/schemas/RecommendationsResponse' + */ router.get('/recommendations', RecommendationsApiMiddleware) + + /** + * @openapi + * /api/healthz: + * get: + * tags: + * - Healthcheck + * description: Responds if the app is up and running + * responses: + * 200: + * description: Responds "OK" if app is up and running + */ router.get('/healthz', (req: express.Request, res: express.Response) => { res.status(200).send('OK') }) diff --git a/packages/create-app/templates/default-app/packages/api/src/middleware.ts b/packages/create-app/templates/default-app/packages/api/src/middleware.ts new file mode 100644 index 000000000..7541fb1c8 --- /dev/null +++ b/packages/create-app/templates/default-app/packages/api/src/middleware.ts @@ -0,0 +1,132 @@ +/* + * © 2023 Thoughtworks, Inc. + */ + +import express from 'express' + +import { + App, + createValidFootprintRequest, + createValidRecommendationsRequest, + FootprintEstimatesRawRequest, + RecommendationsRawRequest, + Tags, +} from '@cloud-carbon-footprint/app' + +import { + EstimationRequestValidationError, + Logger, + PartialDataError, + RecommendationsRequestValidationError, +} from '@cloud-carbon-footprint/common' + +const apiLogger = new Logger('api') + +/** + * Handles the fetching and calculations of cloud footprint estimates for a given date range. + * + * @async + * @param {express.Request} req - The Express request object containing the request parameters. + * @returns A response object with the calculated raw footprint estimates. + */ +export const FootprintApiMiddleware = async function ( + req: express.Request, + res: express.Response, +): Promise { + // Set the request time out to 10 minutes to allow the request enough time to complete. + req.socket.setTimeout(1000 * 60 * 10) + const rawRequest: FootprintEstimatesRawRequest = { + startDate: req.query.start?.toString(), + endDate: req.query.end?.toString(), + ignoreCache: req.query.ignoreCache?.toString(), + groupBy: req.query.groupBy?.toString(), + limit: req.query.limit?.toString(), + skip: req.query.skip?.toString(), + cloudProviders: req.query.cloudProviders as string[], + accounts: req.query.accounts as string[], + services: req.query.services as string[], + regions: req.query.regions as string[], + tags: req.query.tags as Tags, + } + apiLogger.info(`Footprint API request started.`) + if (!rawRequest.groupBy) { + apiLogger.warn('GroupBy parameter not specified, adopting default "day"') + rawRequest.groupBy = 'day' + } + const footprintApp = new App() + try { + const estimationRequest = createValidFootprintRequest(rawRequest) + const estimationResults = await footprintApp.getCostAndEstimates( + estimationRequest, + ) + res.json(estimationResults) + } catch (e) { + apiLogger.error(`Unable to process footprint request.`, e) + if ( + e.constructor.name === + EstimationRequestValidationError.prototype.constructor.name + ) { + res.status(400).send(e.message) + } else if ( + e.constructor.name === PartialDataError.prototype.constructor.name + ) { + res.status(416).send(e.message) + } else res.status(500).send('Internal Server Error') + } +} + +/** + * Handles the fetching of emissions factors for all regions. + * + * @async + * @returns A response object with the mapped emissions factors for each supported cloud provider region. + */ +export const EmissionsApiMiddleware = async function ( + _req: express.Request, + res: express.Response, +): Promise { + apiLogger.info(`Regions emissions factors API request started`) + const footprintApp = new App() + try { + const emissionsResults = await footprintApp.getEmissionsFactors() + res.json(emissionsResults) + } catch (e) { + apiLogger.error(`Unable to process regions emissions factors request.`, e) + res.status(500).send('Internal Server Error') + } +} + +/** + * Handles the fetching of cost saving recommendations along with their calculated carbon and energy savings. + * + * @async + * @param {express.Request} req - The Express request object containing the request parameters. + * @returns A response object with the fetched recommendations and their calculated carbon and energy savings. + */ +export const RecommendationsApiMiddleware = async function ( + req: express.Request, + res: express.Response, +): Promise { + const rawRequest: RecommendationsRawRequest = { + awsRecommendationTarget: req.query.awsRecommendationTarget?.toString(), + } + apiLogger.info(`Recommendations API request started`) + const footprintApp = new App() + try { + const estimationRequest = createValidRecommendationsRequest(rawRequest) + const recommendations = await footprintApp.getRecommendations( + estimationRequest, + ) + res.json(recommendations) + } catch (e) { + apiLogger.error(`Unable to process recommendations request.`, e) + if ( + e.constructor.name === + RecommendationsRequestValidationError.prototype.constructor.name + ) { + res.status(400).send(e.message) + } else { + res.status(500).send('Internal Server Error') + } + } +} diff --git a/packages/create-app/templates/default-app/packages/api/src/server.ts b/packages/create-app/templates/default-app/packages/api/src/server.ts index 617f073f3..c709c128c 100644 --- a/packages/create-app/templates/default-app/packages/api/src/server.ts +++ b/packages/create-app/templates/default-app/packages/api/src/server.ts @@ -11,9 +11,10 @@ import helmet from 'helmet' import cors, { CorsOptions } from 'cors' import { createRouter } from './api' -import auth from './auth' import { Logger, configLoader } from '@cloud-carbon-footprint/common' -import MongoDbCacheManager from '@cloud-carbon-footprint/app/src/MongoDbCacheManager' +import { MongoDbCacheManager } from '@cloud-carbon-footprint/app' +import swaggerDocs from './utils/swagger' +import auth from './utils/auth' const port = process.env.PORT || 4000 const httpApp = express() @@ -47,11 +48,12 @@ if (process.env.ENABLE_CORS) { httpApp.use('/api', createRouter()) -httpApp.listen(port, () => +httpApp.listen(port, () => { serverLogger.info( `Cloud Carbon Footprint Server listening at http://localhost:${port}`, - ), -) + ) + swaggerDocs(httpApp, Number(port)) +}) // Instructions for graceful shutdown process.on('SIGINT', async () => { diff --git a/packages/create-app/templates/default-app/packages/api/src/utils/auth.ts b/packages/create-app/templates/default-app/packages/api/src/utils/auth.ts new file mode 100644 index 000000000..2fbbe24cf --- /dev/null +++ b/packages/create-app/templates/default-app/packages/api/src/utils/auth.ts @@ -0,0 +1,23 @@ +/* + * © 2021 Thoughtworks, Inc. + */ + +import express from 'express' + +import { Logger } from '@cloud-carbon-footprint/common' + +const authLogger = new Logger('auth') + +export default async function ( + req: express.Request, + res: express.Response, + next: express.NextFunction, +): Promise { + try { + authLogger.info('Authentication successful') + next() + } catch (e) { + authLogger.error(`Authentication failed. Error: ${e.message}`, e) + res.status(401).send('Unauthorized') + } +} diff --git a/packages/create-app/templates/default-app/packages/api/src/utils/schemas.yaml b/packages/create-app/templates/default-app/packages/api/src/utils/schemas.yaml new file mode 100644 index 000000000..2bfbbc3e5 --- /dev/null +++ b/packages/create-app/templates/default-app/packages/api/src/utils/schemas.yaml @@ -0,0 +1,85 @@ +# Schemas used for Swagger Documentation (follows OpenAPI Specs) +# These represent types that are defined in the Common package and will need to be updated when its counterpart is changed + +components: + schemas: + FootprintResponse: + type: object + properties: + timestamp: + type: string + serviceEstimates: + type: array + items: + type: object + properties: + cloudProvider: + type: string + enum: [aws, gcp, azure] + kilowattHours: + type: number + co2e: + type: number + cost: + type: number + usesAverageCPUConstant?: + type: boolean + accountId: + type: string + accountName: + type: string + serviceName: + type: string + region: + type: string + tags: + schema: + type: array + items: + type: object + additionalProperties: + type: string + periodStartDate: + type: string + periodEndDate: + type: string + groupBy: + type: string + EmissionResponse: + type: object + properties: + cloudProvider: + type: string + region: + type: string + mtPerKwHour: + type: number + description: metric ton co2e per kilowatt-hour + RecommendationsResponse: + type: object + properties: + cloudProvider: + type: string + enum: [aws, gcp, azure] + accountId: + type: string + accountName: + type: string + region: + type: string + recommendationType: + type: string + recommendationDetail?: + type: string + resourceId?: + type: string + instanceName?: + type: string + kilowattHourSavings: + type: number + costSavings: + type: number + co2eSavings: + type: number + recommendationOptions?: + type: object diff --git a/packages/create-app/templates/default-app/packages/api/src/utils/swagger.ts b/packages/create-app/templates/default-app/packages/api/src/utils/swagger.ts new file mode 100644 index 000000000..5fd4eb409 --- /dev/null +++ b/packages/create-app/templates/default-app/packages/api/src/utils/swagger.ts @@ -0,0 +1,35 @@ +import { Express, Request, Response } from 'express' +import swaggerJsdoc from 'swagger-jsdoc' +import swaggerUi from 'swagger-ui-express' +import { Logger } from '@cloud-carbon-footprint/common' +const serverLogger = new Logger('server') + +const version = '1.6.0' + +const options: swaggerJsdoc.Options = { + definition: { + openapi: '3.0.0', + info: { + title: 'CCF API Docs', + version, + }, + }, + apis: ['./src/api.ts', './src/utils/schemas.yaml'], +} + +const swaggerSpec = swaggerJsdoc(options) + +function swaggerDocs(app: Express, port: number) { + // Swagger page + app.use('/docs', swaggerUi.serve, swaggerUi.setup(swaggerSpec)) + + // Docs in JSON format + app.get('/docs.json', (req: Request, res: Response) => { + res.setHeader('Content-Type', 'application/json') + res.send(swaggerSpec) + }) + + serverLogger.info(`Documentation available at http://localhost:${port}/docs`) +} + +export default swaggerDocs From 45143922722ebcaaecbcdb55e4f185a949e83940 Mon Sep 17 00:00:00 2001 From: Cam Casher Date: Thu, 22 Jun 2023 10:00:07 -0600 Subject: [PATCH 152/251] #1014 resolves hanging gcp recommendations issue --- .../gcp/src/__tests__/ServiceWrapper.test.ts | 38 ++++++++++++++++++- packages/gcp/src/lib/ServiceWrapper.ts | 5 +++ 2 files changed, 41 insertions(+), 2 deletions(-) diff --git a/packages/gcp/src/__tests__/ServiceWrapper.test.ts b/packages/gcp/src/__tests__/ServiceWrapper.test.ts index 4ae7899d0..232924c30 100644 --- a/packages/gcp/src/__tests__/ServiceWrapper.test.ts +++ b/packages/gcp/src/__tests__/ServiceWrapper.test.ts @@ -13,7 +13,7 @@ import { } from '@google-cloud/compute' import { GoogleAuth } from 'google-auth-library' import { RecommenderClient } from '@google-cloud/recommender' -import { GoogleAuthClient, wait } from '@cloud-carbon-footprint/common' +import { GoogleAuthClient, wait, Logger } from '@cloud-carbon-footprint/common' import { mockAddressesResultItems, mockedAddressGetDetails, @@ -286,7 +286,11 @@ describe('GCP Service Wrapper', () => { .mockRejectedValueOnce({}) .mockResolvedValue([[]]) - console.warn = jest.fn().mockResolvedValue('Warn') + const warn = jest + .spyOn(Logger.prototype, 'warn') + .mockImplementation(() => { + return + }) const recommenderIds = ['test-id-1'] await serviceWrapper.getRecommendationsForRecommenderIds( @@ -296,6 +300,36 @@ describe('GCP Service Wrapper', () => { ) expect(wait).toHaveBeenCalled() + expect(warn).toHaveBeenCalledWith( + 'GCP Recommendations API quota exceeded. Retrying after 10 seconds.', + ) + }) + + it('fails to get recommendations from recommender client api calls', async () => { + mockRecommenderClientListRecommendations + .mockRejectedValueOnce({ + details: 'Permission denied', + message: 'Missing permissions for recommender', + }) + .mockRejectedValueOnce({}) + .mockResolvedValue([[]]) + + const warn = jest + .spyOn(Logger.prototype, 'warn') + .mockImplementation(() => { + return + }) + + const recommenderIds = ['test-id-1'] + await serviceWrapper.getRecommendationsForRecommenderIds( + 'test-project-id', + 'us-west1-a', + recommenderIds, + ) + + expect(warn).toHaveBeenCalledWith( + 'Failed to get recommendations for GCP recommender ID: test-id-1. Error: Missing permissions for recommender', + ) }) }) }) diff --git a/packages/gcp/src/lib/ServiceWrapper.ts b/packages/gcp/src/lib/ServiceWrapper.ts index ca4f59f99..5e43eb1bb 100644 --- a/packages/gcp/src/lib/ServiceWrapper.ts +++ b/packages/gcp/src/lib/ServiceWrapper.ts @@ -151,6 +151,11 @@ export default class ServiceWrapper { `GCP Recommendations API quota exceeded. Retrying after ${RETRY_AFTER} seconds.`, ) await wait(RETRY_AFTER * 1000) + } else { + this.serviceWrapperLogger.warn( + `Failed to get recommendations for GCP recommender ID: ${recommenderId}. Error: ${err.message}`, + ) + inProcess = false } } } From 4683b86f8f617be909503ac72cfb9826684878e4 Mon Sep 17 00:00:00 2001 From: Cam Casher Date: Thu, 22 Jun 2023 10:00:44 -0600 Subject: [PATCH 153/251] changeset: patch for gcp --- .changeset/purple-stingrays-obey.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/purple-stingrays-obey.md diff --git a/.changeset/purple-stingrays-obey.md b/.changeset/purple-stingrays-obey.md new file mode 100644 index 000000000..ef178ec28 --- /dev/null +++ b/.changeset/purple-stingrays-obey.md @@ -0,0 +1,5 @@ +--- +'@cloud-carbon-footprint/gcp': patch +--- + +bug fix for gcp package From d217e11e8d353d0840faa4c494b50a5550593035 Mon Sep 17 00:00:00 2001 From: Arik Smith Date: Thu, 22 Jun 2023 11:06:15 -0400 Subject: [PATCH 154/251] changeset: Adds OpenAPI spec documentation and Swagger portal to create-app --- .changeset/lazy-penguins-matter.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/lazy-penguins-matter.md diff --git a/.changeset/lazy-penguins-matter.md b/.changeset/lazy-penguins-matter.md new file mode 100644 index 000000000..d3b158607 --- /dev/null +++ b/.changeset/lazy-penguins-matter.md @@ -0,0 +1,5 @@ +--- +'@cloud-carbon-footprint/create-app': minor +--- + +Adds OpenAPI spec documentation and Swagger portal From d6c120b6b54dc8213c60efea04185005e9c6c2d2 Mon Sep 17 00:00:00 2001 From: Arik Smith Date: Thu, 22 Jun 2023 12:33:04 -0400 Subject: [PATCH 155/251] microsite: adds documentation for additional Azure configuration methods --- .../PerformanceConsiderations.md | 40 ++++++++++++++----- 1 file changed, 29 insertions(+), 11 deletions(-) diff --git a/microsite/docs/ConfigurationOptions/PerformanceConsiderations.md b/microsite/docs/ConfigurationOptions/PerformanceConsiderations.md index cf69fc263..c3bfd45f5 100644 --- a/microsite/docs/ConfigurationOptions/PerformanceConsiderations.md +++ b/microsite/docs/ConfigurationOptions/PerformanceConsiderations.md @@ -41,7 +41,7 @@ Optionally set the date range to query the data starting back in days/weeks/mont - `REACT_APP_DATE_RANGE_TYPE` (example values: day(s), week(s), month(s), etc..) - `REACT_APP_DATE_RANGE_VALUE` (example values: number correlating to day/week/month etc..) -_Note_: If set, these will take least precedence over all other date range configurations. +_Note_: If set, these will take least precedence over all other date range configurations. ### Group By Timestamp in Queries @@ -64,32 +64,50 @@ Due to the required frequency for fetching this amount of data from the API, req In the chance of exceeding rate limits, we have implemented retry logic for each subscription in which we wait the required amount of time specified in the response error before retrying the request again (usually 60 seconds). When this issue is encountered, you will see the following warning logged in the console: -``` zsh +```zsh [ConsumptionManagement] warn: Azure ConsumptionManagementClient UsageDetailRow paging for time range [startDate] to [endDate] failed. Reason: Too many requests. Please retry after 60 seconds. ``` -In this instance, no action is needed and usage data will be fetched after the retry period has passed. However, each subscription can only attempt a *maximum of 10 retries* before it will be skipped and excluded from the calculated estimates. When this occurs, a relevant warning will be logged to the console with details on which subscription was affected. If you encounter this issue, we recommend making a separate request for the affected subscription(s) or to explore one of the configuration options below to reduce the scope of the request. +In this instance, no action is needed and usage data will be fetched after the retry period has passed. However, each subscription can only attempt a _maximum of 10 retries_ before it will be skipped and excluded from the calculated estimates. When this occurs, a relevant warning will be logged to the console with details on which subscription was affected. If you encounter this issue, we recommend making a separate request for the affected subscription(s) or to explore one of the configuration options below to reduce the scope of the request. ### Configuration Options +#### Specifying Subscriptions + +By default, CCF will query usage data for all subscriptions under your configured Azure account. There may be some instances where you would prefer to only calculate estimates for specific subscriptions within an account. To do so, you can provide a list of specific subscription IDs to query using the `AZURE_SUBSCRIPTIONS` variable. This variable should be added to the `.env` file of your `packages/api` or `packages/cli` directory. + +Example: + +```env +AZURE_SUBSCRIPTIONS=["subscription-1", "subscription 2"] +``` + +#### Subscription Chunking + +If you have a large number of subscriptions, you may encounter rate limits when fetching estimates. To reduce the likelihood of this happening across _multiple_ subscriptions, you can customize the number of subscriptions to query at a time. To do this, assign a number to `AZURE_SUBSCRIPTION_CHUNKS` in the `.env` file located in either the `packages/api` or `packages/cli` directories. + +By default, CCF will query usage data in chunks of **10 subscriptions at a time**. You will see the following log confirming the chunk size: + +```sh +Fetching Azure consumption data with ${AZURE.SUBSCRIPTION_CHUNKS} chunk(s). +``` + #### Date Chunking -To aid the frequency of encountered rate limits for a *single* subscription, you can split the date range for the requests into smaller chunks. To enable this feature, we've added a configuration option to do this in your `packages/api/.env` and `packages/cli/.env` files by assigning a number to the `AZURE_CONSUMPTION_CHUNKS_DAYS` variable. +To reduce the frequency of rate limits for a _single_ subscription for a large date range, you can split the date range for the requests into smaller chunks. To enable this feature, we've added a configuration option to do this in your `packages/api/.env` and `packages/cli/.env` files by assigning a number to the `AZURE_CONSUMPTION_CHUNKS_DAYS` variable. In doing so, this will split the requests into smaller chunks of specified days (i.e. assigning a value of 3 will make a request for every 3 days in the requested date range). When enabled, you will see the following log confirming the chunk size: -``` zsh -Time range will be requested in chunks of [chunk size] days. +```sh +Time range will be requested in chunks of ${AZURE_CONSUMPTION_CHUNK_DAYS} days. ``` ...in addition to a similar log for each chunk to indicate the request progress: -``` zsh +```zsh [ConsumptionManagement] debug: Querying consumption usage details from 2023-02-28T00:00:00.000Z to 2023-03-01T23:59:59.999Z ``` -##### Multiple Subscriptions - -You may use this feature with multiple subscriptions, but when doing so, usage data for each subscription will be requested synchronously. This is because making parallel requests would cause rate limits to be encountered more frequently since the chunked requests would be asynchronously made for each subscription -- thus multiplying the number of requests made to the API. +#### Combining Configurations -You are welcome to use this option if exceeding rate limits for large requests still pose an issue for you. Just be aware that the synchronous nature of the requests will increase the time it takes to fetch usage data for your subscriptions, in exchange for less rate limits errors and retry attempts. +You can optimize requests for a large number of subscriptions with large datasets by using both subscription and date chunking together. However, be cautious when using certain configurations for both chunked subscriptions and days, as they may result in more frequent small requests than necessary and increase the likelihood of rate limits. It's best to use both as a last resort and prioritize subscription chunking with an appropriate date range and grouping method provided for the request instead. From 0aacf0e7265ef39b41b67eac5c88333b68613078 Mon Sep 17 00:00:00 2001 From: Arik Smith Date: Thu, 22 Jun 2023 12:57:56 -0400 Subject: [PATCH 156/251] update config glossary and env template descriptions --- .../ConfigurationsGlossary.md | 77 +++++++++++-------- packages/api/.env.template | 4 +- packages/cli/.env.template | 4 +- 3 files changed, 49 insertions(+), 36 deletions(-) diff --git a/microsite/docs/ConfigurationOptions/ConfigurationsGlossary.md b/microsite/docs/ConfigurationOptions/ConfigurationsGlossary.md index 1a3c06b13..ca60f2d4e 100644 --- a/microsite/docs/ConfigurationOptions/ConfigurationsGlossary.md +++ b/microsite/docs/ConfigurationOptions/ConfigurationsGlossary.md @@ -17,16 +17,16 @@ sidebar_position: 4 ### Variables needed for the Billing Data (Holistic) approach with AWS: -| Variable | Example Value | Type | Notes | -| -------------------------------- | --------------------------------------- | ------- | --------------------------------------------------------------------------------------------------------------------------------- | -| AWS_INCLUDE_ESTIMATES | true | boolean | Use this to include AWS estimations. Unset to make this false. Defaults to true. | -| AWS_USE_BILLING_DATA | true | boolean | Use this to configure the application to query Cost and Usage Reports via AWS Athena. | -| AWS_ATHENA_DB_NAME | your-athena-db-name | string | The name of your AWS Athena Database with Cost and Usage Reports data | -| AWS_ATHENA_DB_TABLE | your-athena-db-table | string | The name of your AWS Athena Table with Cost and Usage Reports data | -| AWS_ATHENA_REGION | your-athena-region | string | The region your AWS Athena Database/Table were created in. | -| AWS_ATHENA_QUERY_RESULT_LOCATION | s3://your-athena-query-results-location | string | The AWS S3 Bucket that you want your Athena query results to reside in. Must be prefixed with "s3://". | -| AWS_BILLING_ACCOUNT_ID | your-billing-account-id | string | Your AWS Billing Account ID, where Cost and Usage Reports are configured. | -| AWS_BILLING_ACCOUNT_NAME | your-billing-account-name | string | The name of your AWS Billing Account. This can be any value. | +| Variable | Example Value | Type | Notes | +| -------------------------------- | --------------------------------------- | ------- | ------------------------------------------------------------------------------------------------------ | +| AWS_INCLUDE_ESTIMATES | true | boolean | Use this to include AWS estimations. Unset to make this false. Defaults to true. | +| AWS_USE_BILLING_DATA | true | boolean | Use this to configure the application to query Cost and Usage Reports via AWS Athena. | +| AWS_ATHENA_DB_NAME | your-athena-db-name | string | The name of your AWS Athena Database with Cost and Usage Reports data | +| AWS_ATHENA_DB_TABLE | your-athena-db-table | string | The name of your AWS Athena Table with Cost and Usage Reports data | +| AWS_ATHENA_REGION | your-athena-region | string | The region your AWS Athena Database/Table were created in. | +| AWS_ATHENA_QUERY_RESULT_LOCATION | s3://your-athena-query-results-location | string | The AWS S3 Bucket that you want your Athena query results to reside in. Must be prefixed with "s3://". | +| AWS_BILLING_ACCOUNT_ID | your-billing-account-id | string | Your AWS Billing Account ID, where Cost and Usage Reports are configured. | +| AWS_BILLING_ACCOUNT_NAME | your-billing-account-name | string | The name of your AWS Billing Account. This can be any value. |
@@ -41,20 +41,19 @@ sidebar_position: 4 ### Optionally set these AWS variables: | Variable | Example Value | Type | Notes | -|------------------------------|--------------------------|--------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| ---------------------------- | ------------------------ | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | AWS_AUTH_MODE | default | string | The mode to authenticate with for AWS. Options include: 'AWS': uses [ChainableTemporaryCredentials](https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/ChainableTemporaryCredentials.html), for deploying to AWS. 'GCP': Uses temporary STS Tokens, for deploying to GCP. 'default': Uses default local AWS profile, for local development. | | AWS_PROXY_ACCOUNT_ID | your-proxy-account-id | string | The AWS account of the account to proxy/chain from, when app is deployed to GCP. | | AWS_PROXY_ROLE_NAME | your-proxy-role-name | string | The AWS role name in the proxy account, to proxy/chain from, when app is deployed to GCP. | | AWS_RECOMMENDATIONS_SERVICE | ComputeOptimizer | string | The AWS service used to get recommendations from. Options include: "RightSizing", "ComputeOptimizer" or "All". Default is "Rightsizing". | | AWS_COMPUTE_OPTIMIZER_BUCKET | your-central-bucket-name | string | The name of the AWS bucket in which Compute Optimizer recommendations exist. This is only needed id "ComputeOptimizer" or "All" is configured for the `AWS_RECOMMENDATIONS_SERVICE` variable. | -
### Variables needed for the Billing Data (Holistic) approach with GCP: | Variable | Example Value | Type | Notes | -|--------------------------------|--------------------------------|---------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| ------------------------------ | ------------------------------ | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | GCP_INCLUDE_ESTIMATES | true | boolean | Use this to include GCP estimations. Unset to make this false. Defaults to true. | | GCP_USE_BILLING_DATA | true | boolean | Use this to configure the application to query Billing Export Data via Google BigQuery. | | GOOGLE_APPLICATION_CREDENTIALS | /path/to/your/credentials.json | string | The absolute path to your service account private key file. This service account needs to have permission to query Billing Data using BigQuery. | @@ -75,7 +74,7 @@ sidebar_position: 4 ### Optionally set these GCP variables: | Variable | Example Value | Type | Notes | -|------------------------------------------|---------------|---------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| ---------------------------------------- | ------------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | GCP_USE_CARBON_FREE_ENERGY_PERCENTAGE | true | boolean | Setting this to true will change the emissions factors used by the application to take into account [Google's Carbon Free Energy percentage](https://cloud.google.com/sustainability/region-carbon) in each region. For example in us-central1, the grid emissions factor is 494 gCO2eq/kWh with CFE% of 93%. With this option set to true, the application would instead use 31.78 gCO2eq/kWh. | | GCP_VCPUS_PER_GKE_CLUSTER | 3 | number | Use this to configure the average number of vCPUs the application should use to estimate energy consumption of Kubernetes Engine clusters. If unset, defaults to 3, which is the default number of vCPUs provisioned. | | GCP_VCPUS_PER_CLOUD_COMPOSER_ENVIRONMENT | 14 | number | Use this to configure the average number of vCPUs the application should use to estimate energy consumption of Cloud Composer Environments. If unset, defaults to 14, which is the number of vCPUs provisioned for a medium sized environment. | @@ -84,13 +83,13 @@ sidebar_position: 4 ### Variables needed for the Billing Data (Holistic) approach with Azure: -| Variable | Example Value | Type | Notes | | -| ----------------------- | ------------------------ | ------- | ----------------------------------------------------------------------------------------------------------------- | --- | -| AZURE_INCLUDE_ESTIMATES | true | boolean | Use this to include GCP estimations. Unset to make this false. Defaults to true. | -| AZURE_USE_BILLING_DATA | true | boolean | Use this to configure the application to query Azure Consumption API. | -| AZURE_CLIENT_ID | your-azure-client-id | string | The Azure Service Principal ID with permission to read the Consumption API from your Subscriptions. | -| AZURE_CLIENT_SECRET | your-azure-client-secret | string | The Azure Service Principal Secret with permission to read the Consumption API from your Subscriptions. | -| AZURE_TENANT_ID | your-azure-tenant-id | string | Your Azure tenant ID. | +| Variable | Example Value | Type | Notes | | +| ----------------------- | ------------------------ | ------- | ------------------------------------------------------------------------------------------------------- | --- | +| AZURE_INCLUDE_ESTIMATES | true | boolean | Use this to include GCP estimations. Unset to make this false. Defaults to true. | +| AZURE_USE_BILLING_DATA | true | boolean | Use this to configure the application to query Azure Consumption API. | +| AZURE_CLIENT_ID | your-azure-client-id | string | The Azure Service Principal ID with permission to read the Consumption API from your Subscriptions. | +| AZURE_CLIENT_SECRET | your-azure-client-secret | string | The Azure Service Principal Secret with permission to read the Consumption API from your Subscriptions. | +| AZURE_TENANT_ID | your-azure-tenant-id | string | Your Azure tenant ID. |
@@ -102,6 +101,20 @@ sidebar_position: 4
+### Optionaly include this for tagging support + +| Variable | Example Value | Type | Notes | | +| ------------------------ | -------------------------------------- | ------------- | ---------------------------------------------------------------------------------------------- | --- | +| AZURE_RESOURCE_TAG_NAMES | ["resourceGroup","project","customer"] | array | Azure resource tag names to include if present, include resourceGroup as a tag name if needed. | + +### Optionally set this to customize usage data fetch behavior. See [Azure Performance Considerations](./PerformanceConsiderations.md#azure-performance-considerations) for more information. + +| Variable | Example Value | Type | Notes | | +| ---------------------------- | ------------------------------------ | ------------- | ------------------------------------------------------------------------------------------------------------ | --- | +| AZURE_CONSUMPTION_CHUNK_DAYS | 5 | number | To avoid rate limiting, requests can be grouped in chunks of days. Use this to specify the chunk size | +| AZURE_SUBSCRIPTION_CHUNKS | 10 | number | To avoid rate limiting, a group size of subscipritions for asynchronous requests can be set. Defaults to 10. | +| AZURE_SUBSCRIPTIONS | ["subscription-1", "subscription-2"] | array | List of subscriptions by IDs to include in estimations. Fetches all subscriptions by default | + ### Optionally set this to store cache file in Google Cloud Storage | Variable | Example Value | Type | Notes | @@ -113,21 +126,21 @@ sidebar_position: 4 ### Optionally set these custom configurations for On-Premise calculations -| Variable | Example Value | Type | Notes | -| --------------------- | -------------- | ------ | ----------------------------------------------------------------------------------- | -| ON_PREMISE_CPU_UTILIZATION_SERVER | 40 | number | For on-premise servers, provides an average value for cpu utilization. | -| ON_PREMISE_CPU_UTILIZATION_LAPTOP | 40 | number | For on-premise laptops, provides an average value for cpu utilization. | -| ON_PREMISE_CPU_UTILIZATION_DESKTOP | 40 | number | For on-premise desktops, provides an average value for cpu utilization. | -| ON_PREMISE_AVG_WATTS_SERVER | 300 | number | For on-premise servers, provides an average value for average watts. | -| ON_PREMISE_AVG_WATTS_LAPTOP | 300 | number | For on-premise laptops, provides an average value for average watts. | -| ON_PREMISE_AVG_WATTS_DESKTOP | 300 | number | For on-premise desktops, provides an average value for average watts. | +| Variable | Example Value | Type | Notes | +| ---------------------------------- | ------------- | ------ | ----------------------------------------------------------------------- | +| ON_PREMISE_CPU_UTILIZATION_SERVER | 40 | number | For on-premise servers, provides an average value for cpu utilization. | +| ON_PREMISE_CPU_UTILIZATION_LAPTOP | 40 | number | For on-premise laptops, provides an average value for cpu utilization. | +| ON_PREMISE_CPU_UTILIZATION_DESKTOP | 40 | number | For on-premise desktops, provides an average value for cpu utilization. | +| ON_PREMISE_AVG_WATTS_SERVER | 300 | number | For on-premise servers, provides an average value for average watts. | +| ON_PREMISE_AVG_WATTS_LAPTOP | 300 | number | For on-premise laptops, provides an average value for average watts. | +| ON_PREMISE_AVG_WATTS_DESKTOP | 300 | number | For on-premise desktops, provides an average value for average watts. |
### Optionally set these variables to configure CORS | Varibale | Example Value | Type | Notes | -|-------------------|------------------------------------------|---------|------------------------------------------------------------------------------------------------------------| +| ----------------- | ---------------------------------------- | ------- | ---------------------------------------------------------------------------------------------------------- | | ENABLE_CORS | true | boolean | Enables default CORS headers on all API requests. By default all origins, methods and headers are allowed. | | CORS_ALLOW_ORIGIN | https://example.com,https://example2.com | string | A list of one or more origins to allow for CORS requests, comma separated. | @@ -136,10 +149,10 @@ sidebar_position: 4 ## Client Package - all variables are optional | Variable | Example Value | Type | Notes | -|----------------------------------|-------------------------|---------|-----------------------------------------------------------------------------------------------------------------------------------------------------| +| -------------------------------- | ----------------------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | | REACT_APP_PREVIOUS_YEAR_OF_USAGE | true | boolean | Use this to ensure the application requests usage data from the entire previous calendar year to today. Unset to make this false. Defaults to true. | | REACT_APP_GROUP_BY | month | string | Value to set how the cloud provider queries should return data (e.g. day/week/month/quarter/year). Defaults to day. | | REACT_APP_DATE_RANGE_VALUE | 1 | number | The quantity of REACT_APP_DATE_RANGE_TYPE to be used. | | REACT_APP_DATE_RANGE_TYPE | year | string | The type of time period to be used. Values can be day(s), week(s), month(s), quarter(s), year(s) | | REACT_APP_MINIMAL_DATE_AGE | 1 | number | The amount of days to subtract from current date as end date. | -| REACT_APP_BASE_URL | https://example.com/api | string | The base URL used to make API requests. | \ No newline at end of file +| REACT_APP_BASE_URL | https://example.com/api | string | The base URL used to make API requests. | diff --git a/packages/api/.env.template b/packages/api/.env.template index 3e48e9f5f..feeff39f2 100644 --- a/packages/api/.env.template +++ b/packages/api/.env.template @@ -65,14 +65,14 @@ AZURE_AUTH_MODE=default # Azure resource tag names to include if present, include resourceGroup as a tag name if needed: AZURE_RESOURCE_TAG_NAMES=["resourceGroup"] # eg. ["resourceGroup","project","customer"] -# To avoid rate limiting, azure estimations can be chunked by days (takes precedence over AZURE_SUBSCRIPTION_CHUNKS) +# To avoid rate limiting, azure estimations can be chunked by days AZURE_CONSUMPTION_CHUNKS_DAYS=0 # To avoid rate limiting, asynchronous consumption management calls can be chunked by subscription AZURE_SUBSCRIPTION_CHUNKS=0 # List of Azure subscriptions to include in estimations (all subscriptions are fetched by default) -AZURE_SUBSCRIPTIONS=["subscription-1", "subscription 2"] +AZURE_SUBSCRIPTIONS=["subscription-1", "subscription-2"] # Cache diff --git a/packages/cli/.env.template b/packages/cli/.env.template index 549788f94..626664192 100644 --- a/packages/cli/.env.template +++ b/packages/cli/.env.template @@ -69,11 +69,11 @@ AZURE_RESOURCE_TAG_NAMES=["resourceGroup"] # eg. ["resourceGroup","project","cus # To avoid rate limiting, azure estimations can be chunked by days AZURE_CONSUMPTION_CHUNKS_DAYS=0 -# To avoid rate limiting, asynchronous consumption management calls can be chunked by subscription (takes precedence over AZURE_SUBSCRIPTION_CHUNKS) +# To avoid rate limiting, asynchronous consumption management calls can be chunked by subscription AZURE_SUBSCRIPTION_CHUNKS=0 # List of Azure subscriptions to include in estimations (all subscriptions are fetched by default) -AZURE_SUBSCRIPTIONS=["subscription-1", "subscription 2"] +AZURE_SUBSCRIPTIONS=["subscription-1", "subscription-2"] # Cache From d8e7399a430da7b82c046f103fd48acac19d6c59 Mon Sep 17 00:00:00 2001 From: Arik Smith Date: Thu, 22 Jun 2023 13:32:12 -0400 Subject: [PATCH 157/251] fix formatting errors in config glossary --- .../ConfigurationsGlossary.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/microsite/docs/ConfigurationOptions/ConfigurationsGlossary.md b/microsite/docs/ConfigurationOptions/ConfigurationsGlossary.md index ca60f2d4e..2d5f6a06e 100644 --- a/microsite/docs/ConfigurationOptions/ConfigurationsGlossary.md +++ b/microsite/docs/ConfigurationOptions/ConfigurationsGlossary.md @@ -103,17 +103,17 @@ sidebar_position: 4 ### Optionaly include this for tagging support -| Variable | Example Value | Type | Notes | | -| ------------------------ | -------------------------------------- | ------------- | ---------------------------------------------------------------------------------------------- | --- | -| AZURE_RESOURCE_TAG_NAMES | ["resourceGroup","project","customer"] | array | Azure resource tag names to include if present, include resourceGroup as a tag name if needed. | +| Variable | Example Value | Type | Notes | | +| ------------------------ | -------------------------------------- | ------------ | ---------------------------------------------------------------------------------------------- | --- | +| AZURE_RESOURCE_TAG_NAMES | ["resourceGroup","project","customer"] | array:string | Azure resource tag names to include if present, include resourceGroup as a tag name if needed. | ### Optionally set this to customize usage data fetch behavior. See [Azure Performance Considerations](./PerformanceConsiderations.md#azure-performance-considerations) for more information. -| Variable | Example Value | Type | Notes | | -| ---------------------------- | ------------------------------------ | ------------- | ------------------------------------------------------------------------------------------------------------ | --- | -| AZURE_CONSUMPTION_CHUNK_DAYS | 5 | number | To avoid rate limiting, requests can be grouped in chunks of days. Use this to specify the chunk size | -| AZURE_SUBSCRIPTION_CHUNKS | 10 | number | To avoid rate limiting, a group size of subscipritions for asynchronous requests can be set. Defaults to 10. | -| AZURE_SUBSCRIPTIONS | ["subscription-1", "subscription-2"] | array | List of subscriptions by IDs to include in estimations. Fetches all subscriptions by default | +| Variable | Example Value | Type | Notes | | +| ---------------------------- | ------------------------------------ | ------------ | ------------------------------------------------------------------------------------------------------------ | --- | +| AZURE_CONSUMPTION_CHUNK_DAYS | 5 | number | To avoid rate limiting, requests can be grouped in chunks of days. Use this to specify the chunk size | +| AZURE_SUBSCRIPTION_CHUNKS | 10 | number | To avoid rate limiting, a group size of subscipritions for asynchronous requests can be set. Defaults to 10. | +| AZURE_SUBSCRIPTIONS | ["subscription-1", "subscription-2"] | array:string | List of subscriptions by IDs to include in estimations. Fetches all subscriptions by default | ### Optionally set this to store cache file in Google Cloud Storage From aa46c7a03fa0faf1f4d69012b8a945b6d7f849ca Mon Sep 17 00:00:00 2001 From: Arik Smith Date: Fri, 23 Jun 2023 11:57:41 -0400 Subject: [PATCH 158/251] microsite: add page for running the API and viewing its documentation --- .../GettingStarted/CreatingALookupTable.md | 18 +++++++---- microsite/docs/GettingStarted/Deploying.md | 2 +- .../docs/GettingStarted/RunWithDocker.md | 6 ++-- .../docs/GettingStarted/RunningTheApi.md | 32 +++++++++++++++++++ 4 files changed, 48 insertions(+), 10 deletions(-) create mode 100644 microsite/docs/GettingStarted/RunningTheApi.md diff --git a/microsite/docs/GettingStarted/CreatingALookupTable.md b/microsite/docs/GettingStarted/CreatingALookupTable.md index cc2249f2d..ec29ad63d 100644 --- a/microsite/docs/GettingStarted/CreatingALookupTable.md +++ b/microsite/docs/GettingStarted/CreatingALookupTable.md @@ -2,7 +2,7 @@ id: creating-a-lookup-table title: Creating a Lookup Table slug: /creating-a-lookup-table -sidebar_position: 7 +sidebar_position: 8 --- In order to support the big data processing requirements that some organizations have, it may be more practical or efficient for you to compute carbon metrics within your existing processing. To do so, we support the generation of a lookup table that can be utilized as an additional step in your pipeline. @@ -12,6 +12,7 @@ The lookup table maps the estimated energy (kilowatt-hours) and carbon emissions Once generated, this lookup table (CSV file) can be deployed to your ETL or other data processing pipeline. Then when processing your billing data, you can simply multiply your usage amount by the values in the lookup tables to estimate energy and CO2e. This approach avoids having to use the Cloud Carbon Footprint application code directly, and works regardless of the programming language or environment used in your pipeline. For each service categorization, here are the standard usage units that the lookup table results represent and how you should multiply your usage amount: + - Compute: _Hours_ - Storage: _Terabyte-hours_ - Memory: _Gigabyte-hours_ @@ -21,9 +22,10 @@ To generate this lookup table: 1. Make sure you have a CSV file inside the `cli` package, that contains all the unique region, service name, usage type and usage unit variations in your billing data, along with the vCPUs for that line item, if it exists. You can see an example of this using AWS data [here](https://github.com/cloud-carbon-footprint/cloud-carbon-footprint/blob/trunk/packages/cli/src/__tests__/CreateLookupTable/aws_input.test.csv), and [below](#example-queries-to-create-input-csv-file) for example queries to create this file. 1. Run the following: - - yarn create-lookup-table +```zsh + yarn create-lookup-table +``` The options for this command are: @@ -40,7 +42,8 @@ We would like to thank [@mfulleratlassian](https://github.com/mfulleratlassian) ### AWS - Athena Query - SELECT +```sql + SELECT line_item_product_code as serviceName, product_region as region, line_item_usage_type as usageType, @@ -51,9 +54,11 @@ We would like to thank [@mfulleratlassian](https://github.com/mfulleratlassian) AND line_item_usage_start_date >= DATE('YYYY-MM-DD') AND line_item_usage_start_date <= DATE('YYYY-MM-DD') GROUP BY 1, 2, 3, 4, 5 +``` ### GCP - BigQuery Query +```sql SELECT service.description as serviceName, ifnull(location.region, location.location) as region, @@ -69,13 +74,15 @@ We would like to thank [@mfulleratlassian](https://github.com/mfulleratlassian) AND usage_start_time >= TIMESTAMP('YYYY-MM-DD') AND usage_end_time <= TIMESTAMP('YYYY-MM-DD') GROUP BY serviceName, region, usageType, usageUnit, machineType +``` ### Azure - Yarn Script + Creating an input file for Azure using billing data requires the use of the Consumption Management API rather than a direct query. To assist with this, we have created a script that makes use of your configured [credentials](docs/ConnectingData/Azure.md) in the `packages/cli/.env` file to query and output the needed mappings to a CSV file. To use this script, run the following yarn command with the provided parameters: - + yarn create-azure-lookup The options for this command are: @@ -83,4 +90,3 @@ The options for this command are: --startDate (optional, defaults to 30 days prior to endDate) --endDate (optional, defaults to current date) --output (optional, defaults to "azure_input.csv") - diff --git a/microsite/docs/GettingStarted/Deploying.md b/microsite/docs/GettingStarted/Deploying.md index 4131fa911..163bc5e20 100644 --- a/microsite/docs/GettingStarted/Deploying.md +++ b/microsite/docs/GettingStarted/Deploying.md @@ -2,7 +2,7 @@ id: deploying title: Deploying slug: /deploying -sidebar_position: 8 +sidebar_position: 9 --- ### Deploy to Google App Engine diff --git a/microsite/docs/GettingStarted/RunWithDocker.md b/microsite/docs/GettingStarted/RunWithDocker.md index 357f63100..1685edabf 100644 --- a/microsite/docs/GettingStarted/RunWithDocker.md +++ b/microsite/docs/GettingStarted/RunWithDocker.md @@ -2,7 +2,7 @@ id: run-with-docker title: Run with Docker slug: /run-with-docker -sidebar_position: 5 +sidebar_position: 7 --- If you would like to run with Docker, you'll need install docker and docker-compose: @@ -32,7 +32,7 @@ If you would like to run with Docker, you'll need install docker and docker-comp If you would like to only run the API as a docker container, for example to deploy this as a service for your organization, you can pull and run it with these commands: docker pull cloudcarbonfootprint/api - + docker run \ --env-file packages/api/.env \ --env GOOGLE_APPLICATION_CREDENTIALS=/root/.config/gcloud/service-account-keys.json \ @@ -48,7 +48,7 @@ Then you can access the API at: http://localhost:4000/api/footprint?start=2021-0 If you would like to run the client as a docker container, you can pull and run it with these commands after running API as a docker container: docker pull cloudcarbonfootprint/client:latest - + docker run \ -p 80:80 \ -v ${PWD}/docker/nginx.conf:/etc/nginx/nginx.conf \ diff --git a/microsite/docs/GettingStarted/RunningTheApi.md b/microsite/docs/GettingStarted/RunningTheApi.md new file mode 100644 index 000000000..533cd98f9 --- /dev/null +++ b/microsite/docs/GettingStarted/RunningTheApi.md @@ -0,0 +1,32 @@ +--- +id: running-the-api +title: Running the API +slug: /running-the-api +sidebar_position: 5 +--- + +Sometimes, you may want to receive raw estimates or integrate estimate data into your own dashboard or tool. For this purpose, we've made it possible to run and deploy the API on its own. It allows direct queries for estimates with custom parameters, including more granular request options to specify the exact data you're looking for, including special options for [filtering](../ConfigurationOptions/DataPersistenceAndCaching.md#filtering-estimates). + +#### Running the API Locally + +From the root directory: + +`yarn start-api` + +From the package directory: + +`yarn start` + +## Endpoints + +The API has several endpoints you can query for emissions data: + +- `/footprint` - Gets calculated energy and carbon estimates for a given date range +- `/regions/emissions-factors` - Gets the carbon intensity (CO2e/kWh) of all cloud provider regions +- `/recommendations` - Gets recommendations from cloud providers and their estimated carbon and energy impact + +### Documentation + +Each API endpoint and its parameters have been documented using the OpenAPI Specification language (3.0), which can be parsed or read in the `packages/api/src/api.ts` file. + +For better visibility of this documentation, we have implemented a Swagger UI portal to view and test each endpoint. When running the API locally, this portal is accessible via browser at `localhost:4000/docs`. You can also fetch a JSON version of the documentation by making a GET request to the `/docs.json` endpoint. From 8b0a3799279ec7983cea0a976daf0b65904e33ec Mon Sep 17 00:00:00 2001 From: Marco Valtas Date: Wed, 28 Jun 2023 14:31:13 -0400 Subject: [PATCH 159/251] Avoid creating files for commented out variables in .env --- .talismanrc | 2 +- packages/api/create_docker_secrets.sh | 10 +++++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/.talismanrc b/.talismanrc index ff6cfe214..92651b60a 100644 --- a/.talismanrc +++ b/.talismanrc @@ -116,7 +116,7 @@ fileignoreconfig: - filename: packages/api/.env.template checksum: ec94dc4f50d599e64405562c74b0ba2d1f49781dba1d0fbc9f5011eeea7e2101 - filename: packages/api/create_docker_secrets.sh - checksum: c34b49c2f48a01a21f5a86bc2cc76980231d780fb884d5b829dc595c9ba6d9dd + checksum: 133cdb161e6c312c9a7707e0eb7fc6e05d946d9724e03d5d24a046230c92eb24 - filename: packages/api/create_server_env_file.sh checksum: b6a9b46936e31ca0690ea2fc7080f4c6b5eac0e807c6a71e9dabc13ff8a2d026 - filename: packages/api/estimates.cache-uscs.json diff --git a/packages/api/create_docker_secrets.sh b/packages/api/create_docker_secrets.sh index 0a56d513d..bb058fec7 100755 --- a/packages/api/create_docker_secrets.sh +++ b/packages/api/create_docker_secrets.sh @@ -1,8 +1,8 @@ #!/bin/bash - # -# © 2021 Thoughtworks, Inc. +# © 2023 Thoughtworks, Inc. # +echo "Creating docker secrets from .env file" if [ ! -d $HOME/.docker/secrets ]; then mkdir -p $HOME/.docker/secrets; @@ -12,5 +12,9 @@ while read line; do keyValue=(${line//=/ }) key=${keyValue[0]} value=${keyValue[1]} + + # skip commented out variables + [[ -z $key || $key == \#* ]] && continue + echo $value > $HOME/.docker/secrets/$key -done <.env +done < .env From 197cb18f3598157c16771a834885c9f1595b08b1 Mon Sep 17 00:00:00 2001 From: Cam Casher Date: Fri, 30 Jun 2023 12:59:35 -0600 Subject: [PATCH 160/251] fix: updates client lib exports --- packages/client/src/lib.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/client/src/lib.ts b/packages/client/src/lib.ts index d1e6b40e1..f13257989 100644 --- a/packages/client/src/lib.ts +++ b/packages/client/src/lib.ts @@ -1,5 +1,7 @@ export { default as HeaderBar } from './layout/HeaderBar' +export { default as useFilters } from './common/FilterBar/utils/FilterHook' + export { default as RecommendationsFilterBar } from './pages/RecommendationsPage/RecommendationsFilterBar' export { default as RecommendationsSidePanel } from './pages/RecommendationsPage/RecommendationsSidePanel' export { default as RecommendationsTable } from './pages/RecommendationsPage/RecommendationsTable' @@ -16,3 +18,4 @@ export type { ClientConfig } from './Config' export * from './Types' export * from './utils/hooks' export * from './utils/themes' +export * from './utils/helpers' From 7fc2fe86617336a97a019fd51c5a6420193fb983 Mon Sep 17 00:00:00 2001 From: Cam Casher Date: Fri, 30 Jun 2023 13:04:26 -0600 Subject: [PATCH 161/251] fix: update client imports --- packages/client/src/utils/helpers/handleDates.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/client/src/utils/helpers/handleDates.ts b/packages/client/src/utils/helpers/handleDates.ts index 1f2433320..75e3798b0 100644 --- a/packages/client/src/utils/helpers/handleDates.ts +++ b/packages/client/src/utils/helpers/handleDates.ts @@ -1,6 +1,6 @@ import moment, { Moment, unitOfTime } from 'moment' -import { ClientConfig } from 'src/Config' -import loadConfig from 'src/ConfigLoader' +import { ClientConfig } from '../../Config' +import loadConfig from '../../ConfigLoader' import { ForecastDetails } from '../../pages/RecommendationsPage/RecommendationsTable/Forecast/Forecast' interface dateProps { From e942828dce41c0ac9755916aa366c1b184fdf5d8 Mon Sep 17 00:00:00 2001 From: Cam Casher Date: Fri, 30 Jun 2023 13:15:07 -0600 Subject: [PATCH 162/251] changeset: client patch --- .changeset/happy-dryers-double.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/happy-dryers-double.md diff --git a/.changeset/happy-dryers-double.md b/.changeset/happy-dryers-double.md new file mode 100644 index 000000000..d9c5e8fc2 --- /dev/null +++ b/.changeset/happy-dryers-double.md @@ -0,0 +1,5 @@ +--- +'@cloud-carbon-footprint/client': patch +--- + +updates imports/exports for plugin compatibility From 5eaf24ba853cc719672c25f9eb9c5b0514864f24 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 30 Jun 2023 19:17:37 +0000 Subject: [PATCH 163/251] Version Packages --- .changeset/angry-seals-camp.md | 5 ----- .changeset/dry-mails-cough.md | 6 ------ .changeset/funny-candles-hide.md | 6 ------ .changeset/fuzzy-bulldogs-sing.md | 5 ----- .changeset/happy-dryers-double.md | 5 ----- .changeset/lazy-penguins-matter.md | 5 ----- .changeset/light-socks-add.md | 5 ----- .changeset/moody-phones-teach.md | 5 ----- .changeset/purple-stingrays-obey.md | 5 ----- .changeset/rare-parents-know.md | 10 ---------- .changeset/unlucky-hotels-tease.md | 5 ----- packages/api/CHANGELOG.md | 18 ++++++++++++++++++ packages/api/package.json | 6 +++--- packages/app/CHANGELOG.md | 18 ++++++++++++++++++ packages/app/package.json | 8 ++++---- packages/azure/CHANGELOG.md | 13 +++++++++++++ packages/azure/package.json | 4 ++-- packages/cli/CHANGELOG.md | 18 ++++++++++++++++++ packages/cli/package.json | 6 +++--- packages/client/CHANGELOG.md | 10 ++++++++++ packages/client/package.json | 4 ++-- packages/common/CHANGELOG.md | 7 +++++++ packages/common/package.json | 2 +- packages/create-app/CHANGELOG.md | 13 +++++++++++++ packages/create-app/package.json | 2 +- packages/gcp/CHANGELOG.md | 9 +++++++++ packages/gcp/package.json | 4 ++-- yarn.lock | 28 ++++++++++++++-------------- 28 files changed, 138 insertions(+), 94 deletions(-) delete mode 100644 .changeset/angry-seals-camp.md delete mode 100644 .changeset/dry-mails-cough.md delete mode 100644 .changeset/funny-candles-hide.md delete mode 100644 .changeset/fuzzy-bulldogs-sing.md delete mode 100644 .changeset/happy-dryers-double.md delete mode 100644 .changeset/lazy-penguins-matter.md delete mode 100644 .changeset/light-socks-add.md delete mode 100644 .changeset/moody-phones-teach.md delete mode 100644 .changeset/purple-stingrays-obey.md delete mode 100644 .changeset/rare-parents-know.md delete mode 100644 .changeset/unlucky-hotels-tease.md diff --git a/.changeset/angry-seals-camp.md b/.changeset/angry-seals-camp.md deleted file mode 100644 index 3f0d99fe8..000000000 --- a/.changeset/angry-seals-camp.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@cloud-carbon-footprint/azure': minor ---- - -Adds config support for chunking/splitting azure requests by subscription diff --git a/.changeset/dry-mails-cough.md b/.changeset/dry-mails-cough.md deleted file mode 100644 index 6745617b0..000000000 --- a/.changeset/dry-mails-cough.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@cloud-carbon-footprint/azure': minor -'@cloud-carbon-footprint/common': minor ---- - -Adds support for listing Azure subscription IDs to fetch diff --git a/.changeset/funny-candles-hide.md b/.changeset/funny-candles-hide.md deleted file mode 100644 index b44a666bd..000000000 --- a/.changeset/funny-candles-hide.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@cloud-carbon-footprint/cli': minor -'@cloud-carbon-footprint/common': minor ---- - -Adds config support for chunking/splitting azure requests by subscription diff --git a/.changeset/fuzzy-bulldogs-sing.md b/.changeset/fuzzy-bulldogs-sing.md deleted file mode 100644 index 159184ba9..000000000 --- a/.changeset/fuzzy-bulldogs-sing.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@cloud-carbon-footprint/client': patch ---- - -Improves recommendations forecast accuracy, and fixes cost formatting errors for local currencies diff --git a/.changeset/happy-dryers-double.md b/.changeset/happy-dryers-double.md deleted file mode 100644 index d9c5e8fc2..000000000 --- a/.changeset/happy-dryers-double.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@cloud-carbon-footprint/client': patch ---- - -updates imports/exports for plugin compatibility diff --git a/.changeset/lazy-penguins-matter.md b/.changeset/lazy-penguins-matter.md deleted file mode 100644 index d3b158607..000000000 --- a/.changeset/lazy-penguins-matter.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@cloud-carbon-footprint/create-app': minor ---- - -Adds OpenAPI spec documentation and Swagger portal diff --git a/.changeset/light-socks-add.md b/.changeset/light-socks-add.md deleted file mode 100644 index 6986793ca..000000000 --- a/.changeset/light-socks-add.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@cloud-carbon-footprint/cli': patch ---- - -Adds support for listing Azure subscription IDs to fetch diff --git a/.changeset/moody-phones-teach.md b/.changeset/moody-phones-teach.md deleted file mode 100644 index 8b44b5b76..000000000 --- a/.changeset/moody-phones-teach.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@cloud-carbon-footprint/create-app': patch ---- - -Updates env templates with new configuration options diff --git a/.changeset/purple-stingrays-obey.md b/.changeset/purple-stingrays-obey.md deleted file mode 100644 index ef178ec28..000000000 --- a/.changeset/purple-stingrays-obey.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@cloud-carbon-footprint/gcp': patch ---- - -bug fix for gcp package diff --git a/.changeset/rare-parents-know.md b/.changeset/rare-parents-know.md deleted file mode 100644 index d54e6a784..000000000 --- a/.changeset/rare-parents-know.md +++ /dev/null @@ -1,10 +0,0 @@ ---- -'@cloud-carbon-footprint/api': minor -'@cloud-carbon-footprint/app': minor -'@cloud-carbon-footprint/cli': minor -'@cloud-carbon-footprint/create-app': minor ---- - -API and CLI processes now persist connection to mongodb client when enabled as cache - -For Create-App changes, please refer to this [commit](https://github.com/cloud-carbon-footprint/cloud-carbon-footprint/commit/8995b8a7f29fb06f8a437166d32e75b1ed147870). \ No newline at end of file diff --git a/.changeset/unlucky-hotels-tease.md b/.changeset/unlucky-hotels-tease.md deleted file mode 100644 index 5f56b94d7..000000000 --- a/.changeset/unlucky-hotels-tease.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@cloud-carbon-footprint/api': minor ---- - -Adds OpenAPI spec documentation and Swagger portal diff --git a/packages/api/CHANGELOG.md b/packages/api/CHANGELOG.md index f2383a98d..bd4148454 100644 --- a/packages/api/CHANGELOG.md +++ b/packages/api/CHANGELOG.md @@ -1,5 +1,23 @@ # @cloud-carbon-footprint/api +## 1.7.0 + +### Minor Changes + +- fedf79c0: API and CLI processes now persist connection to mongodb client when enabled as cache + + For Create-App changes, please refer to this [commit](https://github.com/cloud-carbon-footprint/cloud-carbon-footprint/commit/8995b8a7f29fb06f8a437166d32e75b1ed147870). + +- 045ef419: Adds OpenAPI spec documentation and Swagger portal + +### Patch Changes + +- Updated dependencies [1a5d7636] +- Updated dependencies [c82bf5fd] +- Updated dependencies [fedf79c0] + - @cloud-carbon-footprint/common@1.12.0 + - @cloud-carbon-footprint/app@1.3.0 + ## 1.6.2 ### Patch Changes diff --git a/packages/api/package.json b/packages/api/package.json index 574fe3102..8fed43437 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -1,6 +1,6 @@ { "name": "@cloud-carbon-footprint/api", - "version": "1.6.2", + "version": "1.7.0", "license": "Apache-2.0", "description": "The API endpoint as an entrypoint to get cloud energy and carbon emissions. Optionally used by the client dashboard.", "main": "src/server.ts", @@ -73,8 +73,8 @@ "typescript": "^4.6.2" }, "dependencies": { - "@cloud-carbon-footprint/app": "^1.2.1", - "@cloud-carbon-footprint/common": "^1.9.0", + "@cloud-carbon-footprint/app": "^1.3.0", + "@cloud-carbon-footprint/common": "^1.12.0", "@types/express": "^4.17.12", "cors": "^2.8.5", "express": "^4.17.1", diff --git a/packages/app/CHANGELOG.md b/packages/app/CHANGELOG.md index f4bedbbe8..7259f833a 100644 --- a/packages/app/CHANGELOG.md +++ b/packages/app/CHANGELOG.md @@ -1,5 +1,23 @@ # @cloud-carbon-footprint/app +## 1.3.0 + +### Minor Changes + +- fedf79c0: API and CLI processes now persist connection to mongodb client when enabled as cache + + For Create-App changes, please refer to this [commit](https://github.com/cloud-carbon-footprint/cloud-carbon-footprint/commit/8995b8a7f29fb06f8a437166d32e75b1ed147870). + +### Patch Changes + +- Updated dependencies [c82bf5fd] +- Updated dependencies [1a5d7636] +- Updated dependencies [c82bf5fd] +- Updated dependencies [4683b86f] + - @cloud-carbon-footprint/azure@1.4.0 + - @cloud-carbon-footprint/common@1.12.0 + - @cloud-carbon-footprint/gcp@0.13.1 + ## 1.2.2 ### Patch Changes diff --git a/packages/app/package.json b/packages/app/package.json index 3da047a48..89ae03920 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -1,6 +1,6 @@ { "name": "@cloud-carbon-footprint/app", - "version": "1.2.2", + "version": "1.3.0", "license": "Apache-2.0", "description": "The logic to bootstrap the cloud-carbon-footprint server-side application", "main": "src/index.ts", @@ -41,9 +41,9 @@ }, "dependencies": { "@cloud-carbon-footprint/aws": "^0.14.4", - "@cloud-carbon-footprint/azure": "^1.3.0", - "@cloud-carbon-footprint/common": "^1.11.0", - "@cloud-carbon-footprint/gcp": "^0.13.0", + "@cloud-carbon-footprint/azure": "^1.4.0", + "@cloud-carbon-footprint/common": "^1.12.0", + "@cloud-carbon-footprint/gcp": "^0.13.1", "@cloud-carbon-footprint/on-premise": "^0.1.1", "@google-cloud/storage": "^5.16.1", "@sovpro/delimited-stream": "^1.1.0", diff --git a/packages/azure/CHANGELOG.md b/packages/azure/CHANGELOG.md index 255627bc6..5c8a03284 100644 --- a/packages/azure/CHANGELOG.md +++ b/packages/azure/CHANGELOG.md @@ -1,5 +1,18 @@ # @cloud-carbon-footprint/azure +## 1.4.0 + +### Minor Changes + +- c82bf5fd: Adds config support for chunking/splitting azure requests by subscription +- 1a5d7636: Adds support for listing Azure subscription IDs to fetch + +### Patch Changes + +- Updated dependencies [1a5d7636] +- Updated dependencies [c82bf5fd] + - @cloud-carbon-footprint/common@1.12.0 + ## 1.3.0 ### Minor Changes diff --git a/packages/azure/package.json b/packages/azure/package.json index 54f55d37e..845eedc75 100644 --- a/packages/azure/package.json +++ b/packages/azure/package.json @@ -1,6 +1,6 @@ { "name": "@cloud-carbon-footprint/azure", - "version": "1.3.0", + "version": "1.4.0", "license": "Apache-2.0", "description": "The core logic to get cloud usage data and estimate energy and carbon emissions from Microsoft Azure.", "main": "src/index.ts", @@ -47,7 +47,7 @@ "@azure/identity": "^3.2.2", "@azure/ms-rest-js": "^2.6.6", "@azure/ms-rest-nodeauth": "^3.1.1", - "@cloud-carbon-footprint/common": "^1.10.0", + "@cloud-carbon-footprint/common": "^1.12.0", "@cloud-carbon-footprint/core": "^0.17.3", "@google-cloud/secret-manager": "^3.10.1", "moment": "^2.29.1" diff --git a/packages/cli/CHANGELOG.md b/packages/cli/CHANGELOG.md index a7f093089..01be20a6f 100644 --- a/packages/cli/CHANGELOG.md +++ b/packages/cli/CHANGELOG.md @@ -1,5 +1,23 @@ # @cloud-carbon-footprint/cli +## 1.11.0 + +### Minor Changes + +- c82bf5fd: Adds config support for chunking/splitting azure requests by subscription +- fedf79c0: API and CLI processes now persist connection to mongodb client when enabled as cache + + For Create-App changes, please refer to this [commit](https://github.com/cloud-carbon-footprint/cloud-carbon-footprint/commit/8995b8a7f29fb06f8a437166d32e75b1ed147870). + +### Patch Changes + +- 1a5d7636: Adds support for listing Azure subscription IDs to fetch +- Updated dependencies [1a5d7636] +- Updated dependencies [c82bf5fd] +- Updated dependencies [fedf79c0] + - @cloud-carbon-footprint/common@1.12.0 + - @cloud-carbon-footprint/app@1.3.0 + ## 1.10.2 ### Patch Changes diff --git a/packages/cli/package.json b/packages/cli/package.json index 01a854769..dd7955fe3 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@cloud-carbon-footprint/cli", - "version": "1.10.2", + "version": "1.11.0", "license": "Apache-2.0", "description": "Command Line Interface as an entrypoint to get cloud energy and carbon emissions.", "main": "src/index.ts", @@ -70,8 +70,8 @@ "dependencies": { "@azure/arm-consumption": "^9.2.0", "@azure/arm-resources-subscriptions": "^2.0.2", - "@cloud-carbon-footprint/app": "1.2.2", - "@cloud-carbon-footprint/common": "^1.11.0", + "@cloud-carbon-footprint/app": "1.3.0", + "@cloud-carbon-footprint/common": "^1.12.0", "@types/cli-table": "^0.3.0", "@types/prompts": "^2.0.12", "@types/ramda": "^0.29.0", diff --git a/packages/client/CHANGELOG.md b/packages/client/CHANGELOG.md index 9d98bfd81..ae1a875b8 100644 --- a/packages/client/CHANGELOG.md +++ b/packages/client/CHANGELOG.md @@ -1,5 +1,15 @@ # @cloud-carbon-footprint/client +## 4.1.3 + +### Patch Changes + +- 3d7a21ff: Improves recommendations forecast accuracy, and fixes cost formatting errors for local currencies +- e942828d: updates imports/exports for plugin compatibility +- Updated dependencies [1a5d7636] +- Updated dependencies [c82bf5fd] + - @cloud-carbon-footprint/common@1.12.0 + ## 4.1.2 ### Patch Changes diff --git a/packages/client/package.json b/packages/client/package.json index 2004b7e95..9c2c02489 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -1,6 +1,6 @@ { "name": "@cloud-carbon-footprint/client", - "version": "4.1.2", + "version": "4.1.3", "license": "Apache-2.0", "description": "The front-end dashboard for Cloud Carbon Footprint.", "main": "src/index.tsx", @@ -24,7 +24,7 @@ ], "dependencies": { "@babel/runtime": "^7.14.0", - "@cloud-carbon-footprint/common": "^1.10.0", + "@cloud-carbon-footprint/common": "^1.12.0", "@emotion/react": "^11.9.0", "@emotion/styled": "^11.8.1", "@material-ui/core": "^4.11.4", diff --git a/packages/common/CHANGELOG.md b/packages/common/CHANGELOG.md index 27829dbd0..c4e01f263 100644 --- a/packages/common/CHANGELOG.md +++ b/packages/common/CHANGELOG.md @@ -1,5 +1,12 @@ # @cloud-carbon-footprint/common +## 1.12.0 + +### Minor Changes + +- 1a5d7636: Adds support for listing Azure subscription IDs to fetch +- c82bf5fd: Adds config support for chunking/splitting azure requests by subscription + ## 1.11.0 ### Minor Changes diff --git a/packages/common/package.json b/packages/common/package.json index 3086c8a9c..e69099817 100644 --- a/packages/common/package.json +++ b/packages/common/package.json @@ -1,6 +1,6 @@ { "name": "@cloud-carbon-footprint/common", - "version": "1.11.0", + "version": "1.12.0", "license": "Apache-2.0", "description": "Common functionality to be shared among other cloud carbon footprint packages", "main": "src/index.ts", diff --git a/packages/create-app/CHANGELOG.md b/packages/create-app/CHANGELOG.md index 2bc227c34..3867bf774 100644 --- a/packages/create-app/CHANGELOG.md +++ b/packages/create-app/CHANGELOG.md @@ -1,5 +1,18 @@ # @cloud-carbon-footprint/create-app +## 2.4.0 + +### Minor Changes + +- d217e11e: Adds OpenAPI spec documentation and Swagger portal +- fedf79c0: API and CLI processes now persist connection to mongodb client when enabled as cache + + For Create-App changes, please refer to this [commit](https://github.com/cloud-carbon-footprint/cloud-carbon-footprint/commit/8995b8a7f29fb06f8a437166d32e75b1ed147870). + +### Patch Changes + +- 7688b086: Updates env templates with new configuration options + ## 2.3.5 ### Patch Changes diff --git a/packages/create-app/package.json b/packages/create-app/package.json index 4c1bb2932..78e70dbe0 100644 --- a/packages/create-app/package.json +++ b/packages/create-app/package.json @@ -1,6 +1,6 @@ { "name": "@cloud-carbon-footprint/create-app", - "version": "2.3.5", + "version": "2.4.0", "license": "Apache-2.0", "description": "Create app package for Cloud Carbon Footprint", "main": "dist/index.js", diff --git a/packages/gcp/CHANGELOG.md b/packages/gcp/CHANGELOG.md index 0a027d92c..a7b354a73 100644 --- a/packages/gcp/CHANGELOG.md +++ b/packages/gcp/CHANGELOG.md @@ -1,5 +1,14 @@ # @cloud-carbon-footprint/gcp +## 0.13.1 + +### Patch Changes + +- 4683b86f: bug fix for gcp package +- Updated dependencies [1a5d7636] +- Updated dependencies [c82bf5fd] + - @cloud-carbon-footprint/common@1.12.0 + ## 0.13.0 ### Minor Changes diff --git a/packages/gcp/package.json b/packages/gcp/package.json index d0074ccf1..1f464ca62 100644 --- a/packages/gcp/package.json +++ b/packages/gcp/package.json @@ -1,6 +1,6 @@ { "name": "@cloud-carbon-footprint/gcp", - "version": "0.13.0", + "version": "0.13.1", "license": "Apache-2.0", "description": "The core logic to get cloud usage data and estimate energy and carbon emissions from Google Cloud Platform.", "main": "src/index.ts", @@ -41,7 +41,7 @@ "lint:fix": "eslint '*/**/*.ts' --quiet --fix" }, "dependencies": { - "@cloud-carbon-footprint/common": "^1.11.0", + "@cloud-carbon-footprint/common": "^1.12.0", "@cloud-carbon-footprint/core": "^0.17.3", "@google-cloud/bigquery": "^5.9.3", "@google-cloud/compute": "^3.9.0", diff --git a/yarn.lock b/yarn.lock index 4cdac328c..38009b091 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2250,8 +2250,8 @@ __metadata: version: 0.0.0-use.local resolution: "@cloud-carbon-footprint/api@workspace:packages/api" dependencies: - "@cloud-carbon-footprint/app": ^1.2.1 - "@cloud-carbon-footprint/common": ^1.9.0 + "@cloud-carbon-footprint/app": ^1.3.0 + "@cloud-carbon-footprint/common": ^1.12.0 "@types/cors": ^2.8.12 "@types/express": ^4.17.12 "@types/jest": ^27.4.0 @@ -2288,14 +2288,14 @@ __metadata: languageName: unknown linkType: soft -"@cloud-carbon-footprint/app@1.2.2, @cloud-carbon-footprint/app@^1.2.1, @cloud-carbon-footprint/app@workspace:packages/app": +"@cloud-carbon-footprint/app@1.3.0, @cloud-carbon-footprint/app@^1.3.0, @cloud-carbon-footprint/app@workspace:packages/app": version: 0.0.0-use.local resolution: "@cloud-carbon-footprint/app@workspace:packages/app" dependencies: "@cloud-carbon-footprint/aws": ^0.14.4 - "@cloud-carbon-footprint/azure": ^1.3.0 - "@cloud-carbon-footprint/common": ^1.11.0 - "@cloud-carbon-footprint/gcp": ^0.13.0 + "@cloud-carbon-footprint/azure": ^1.4.0 + "@cloud-carbon-footprint/common": ^1.12.0 + "@cloud-carbon-footprint/gcp": ^0.13.1 "@cloud-carbon-footprint/on-premise": ^0.1.1 "@google-cloud/storage": ^5.16.1 "@sovpro/delimited-stream": ^1.1.0 @@ -2357,7 +2357,7 @@ __metadata: languageName: unknown linkType: soft -"@cloud-carbon-footprint/azure@^1.3.0, @cloud-carbon-footprint/azure@workspace:packages/azure": +"@cloud-carbon-footprint/azure@^1.4.0, @cloud-carbon-footprint/azure@workspace:packages/azure": version: 0.0.0-use.local resolution: "@cloud-carbon-footprint/azure@workspace:packages/azure" dependencies: @@ -2367,7 +2367,7 @@ __metadata: "@azure/identity": ^3.2.2 "@azure/ms-rest-js": ^2.6.6 "@azure/ms-rest-nodeauth": ^3.1.1 - "@cloud-carbon-footprint/common": ^1.10.0 + "@cloud-carbon-footprint/common": ^1.12.0 "@cloud-carbon-footprint/core": ^0.17.3 "@google-cloud/secret-manager": ^3.10.1 "@types/jest": ^27.4.0 @@ -2401,8 +2401,8 @@ __metadata: dependencies: "@azure/arm-consumption": ^9.2.0 "@azure/arm-resources-subscriptions": ^2.0.2 - "@cloud-carbon-footprint/app": 1.2.2 - "@cloud-carbon-footprint/common": ^1.11.0 + "@cloud-carbon-footprint/app": 1.3.0 + "@cloud-carbon-footprint/common": ^1.12.0 "@types/cli-table": ^0.3.0 "@types/jest": ^27.4.0 "@types/jest-when": ^3.5.0 @@ -2446,7 +2446,7 @@ __metadata: resolution: "@cloud-carbon-footprint/client@workspace:packages/client" dependencies: "@babel/runtime": ^7.14.0 - "@cloud-carbon-footprint/common": ^1.10.0 + "@cloud-carbon-footprint/common": ^1.12.0 "@emotion/react": ^11.9.0 "@emotion/styled": ^11.8.1 "@material-ui/core": ^4.11.4 @@ -2504,7 +2504,7 @@ __metadata: languageName: unknown linkType: soft -"@cloud-carbon-footprint/common@^1.10.0, @cloud-carbon-footprint/common@^1.11.0, @cloud-carbon-footprint/common@^1.8.0, @cloud-carbon-footprint/common@^1.9.0, @cloud-carbon-footprint/common@workspace:packages/common": +"@cloud-carbon-footprint/common@^1.10.0, @cloud-carbon-footprint/common@^1.12.0, @cloud-carbon-footprint/common@^1.8.0, @cloud-carbon-footprint/common@workspace:packages/common": version: 0.0.0-use.local resolution: "@cloud-carbon-footprint/common@workspace:packages/common" dependencies: @@ -2594,11 +2594,11 @@ __metadata: languageName: unknown linkType: soft -"@cloud-carbon-footprint/gcp@^0.13.0, @cloud-carbon-footprint/gcp@workspace:packages/gcp": +"@cloud-carbon-footprint/gcp@^0.13.1, @cloud-carbon-footprint/gcp@workspace:packages/gcp": version: 0.0.0-use.local resolution: "@cloud-carbon-footprint/gcp@workspace:packages/gcp" dependencies: - "@cloud-carbon-footprint/common": ^1.11.0 + "@cloud-carbon-footprint/common": ^1.12.0 "@cloud-carbon-footprint/core": ^0.17.3 "@google-cloud/bigquery": ^5.9.3 "@google-cloud/compute": ^3.9.0 From 6d5202c25964bdb0f3cd26eef45be1cecb0a4529 Mon Sep 17 00:00:00 2001 From: Marco Valtas Date: Fri, 30 Jun 2023 12:23:34 -0400 Subject: [PATCH 164/251] Updates docker-compose to handle MONGODB credentials --- .talismanrc | 2 +- docker-compose.yml | 9 ++++++++- packages/api/create_docker_secrets.sh | 22 +++++++++++++++++++++- 3 files changed, 30 insertions(+), 3 deletions(-) diff --git a/.talismanrc b/.talismanrc index 92651b60a..e4e55cdfe 100644 --- a/.talismanrc +++ b/.talismanrc @@ -116,7 +116,7 @@ fileignoreconfig: - filename: packages/api/.env.template checksum: ec94dc4f50d599e64405562c74b0ba2d1f49781dba1d0fbc9f5011eeea7e2101 - filename: packages/api/create_docker_secrets.sh - checksum: 133cdb161e6c312c9a7707e0eb7fc6e05d946d9724e03d5d24a046230c92eb24 + checksum: 6413d65edef7f51e4a0def0f8f5eb665f389e30cf19d3b02f71ae9f9cd7aa500 - filename: packages/api/create_server_env_file.sh checksum: b6a9b46936e31ca0690ea2fc7080f4c6b5eac0e807c6a71e9dabc13ff8a2d026 - filename: packages/api/estimates.cache-uscs.json diff --git a/docker-compose.yml b/docker-compose.yml index aecc2da35..e396bb119 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -30,6 +30,8 @@ services: - AZURE_CLIENT_ID - AZURE_CLIENT_SECRET - AZURE_TENANT_ID + - MONGODB_URI + - MONGODB_CREDENTIALS environment: - AWS_USE_BILLING_DATA=true - AWS_TARGET_ACCOUNT_ROLE_NAME=/run/secrets/AWS_TARGET_ACCOUNT_ROLE_NAME @@ -49,7 +51,8 @@ services: - AZURE_CLIENT_ID=/run/secrets/AZURE_CLIENT_ID - AZURE_CLIENT_SECRET=/run/secrets/AZURE_CLIENT_SECRET - AZURE_TENANT_ID=/run/secrets/AZURE_TENANT_ID - + - MONGODB_URI=/run/secrets/MONGODB_URI + - MONGODB_CREDENTIALS=/run/secrets/MONGODB_CREDENTIALS secrets: AWS_TARGET_ACCOUNT_ROLE_NAME: file: ~/.docker/secrets/AWS_TARGET_ACCOUNT_ROLE_NAME @@ -77,3 +80,7 @@ secrets: file: ~/.docker/secrets/AZURE_CLIENT_SECRET AZURE_TENANT_ID: file: ~/.docker/secrets/AZURE_TENANT_ID + MONGODB_URI: + file: ~/.docker/secrets/MONGODB_URI + MONGODB_CREDENTIALS: + file: ~/.docker/secrets/MONGODB_CREDENTIALS diff --git a/packages/api/create_docker_secrets.sh b/packages/api/create_docker_secrets.sh index bb058fec7..ba967c1be 100755 --- a/packages/api/create_docker_secrets.sh +++ b/packages/api/create_docker_secrets.sh @@ -2,12 +2,32 @@ # # © 2023 Thoughtworks, Inc. # -echo "Creating docker secrets from .env file" +echo "Creating docker secrets" +# Ensure that the secrets directory exists if [ ! -d $HOME/.docker/secrets ]; then mkdir -p $HOME/.docker/secrets; fi +# Create dummy secrets entries for all secrets in docker-compose.yml +# later we fill these entries with values from .env file. Otherwise +# docker-compose will complain about missing secrets. +inside_secrets=0 +while IFS= read -r line; do + if [[ $line == "secrets:"* ]]; then + inside_secrets=1 + elif [[ $line == " "* ]]; then # line is a value + if (( inside_secrets )); then + key=$(echo "$line" | awk -F: '{print $1}' | sed 's/^[ \t]*//;s/[ \t]*$//') + [[ ${key} == "file" ]] && continue # skip 'file:' entries + touch $HOME/.docker/secrets/$key + fi + else + inside_secrets=0 + fi +done < ../../docker-compose.yml + +# Fill secrets with values from .env file while read line; do keyValue=(${line//=/ }) key=${keyValue[0]} From c7231420289985f8e00a0815bb0672812af5dcfb Mon Sep 17 00:00:00 2001 From: Marco Valtas Date: Wed, 5 Jul 2023 14:12:07 -0400 Subject: [PATCH 165/251] Adds MongoDB connection configuration example --- docker-compose.yml | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index e396bb119..38b350b5d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -32,7 +32,11 @@ services: - AZURE_TENANT_ID - MONGODB_URI - MONGODB_CREDENTIALS + # uncomment to define this as the certificate file for your MongoDB + #- x509-cert-mongodb.pem environment: + # set the CACHE_MODE to MONGODB to use MongoDB + - CACHE_MODE=LOCAL - AWS_USE_BILLING_DATA=true - AWS_TARGET_ACCOUNT_ROLE_NAME=/run/secrets/AWS_TARGET_ACCOUNT_ROLE_NAME - AWS_ATHENA_DB_NAME=/run/secrets/AWS_ATHENA_DB_NAME @@ -51,8 +55,10 @@ services: - AZURE_CLIENT_ID=/run/secrets/AZURE_CLIENT_ID - AZURE_CLIENT_SECRET=/run/secrets/AZURE_CLIENT_SECRET - AZURE_TENANT_ID=/run/secrets/AZURE_TENANT_ID - - MONGODB_URI=/run/secrets/MONGODB_URI - - MONGODB_CREDENTIALS=/run/secrets/MONGODB_CREDENTIALS + # uncomment to enable MongoDB + #- MONGODB_URI=/run/secrets/MONGODB_URI + #- MONGODB_CREDENTIALS=/run/secrets/MONGODB_CREDENTIALS + #- x509-cert-mongodb.pem=/run/secrets/x509-cert-mongodb.pem secrets: AWS_TARGET_ACCOUNT_ROLE_NAME: file: ~/.docker/secrets/AWS_TARGET_ACCOUNT_ROLE_NAME @@ -84,3 +90,6 @@ secrets: file: ~/.docker/secrets/MONGODB_URI MONGODB_CREDENTIALS: file: ~/.docker/secrets/MONGODB_CREDENTIALS + # uncomment to refer to your MongoDB cert file + #x509-cert-mongodb.pem: + # file: ~/.docker/secrets/x509-cert-mongodb.pem From 3cd1dbd839c953f834e82110e0fff4be9f620418 Mon Sep 17 00:00:00 2001 From: Arik Smith Date: Wed, 5 Jul 2023 23:22:17 -0400 Subject: [PATCH 166/251] add glue query for checking athena table schema --- .../aws/src/__tests__/ServiceWrapper.test.ts | 34 +++++++++++++++++++ packages/aws/src/lib/ServiceWrapper.ts | 16 ++++++++- 2 files changed, 49 insertions(+), 1 deletion(-) diff --git a/packages/aws/src/__tests__/ServiceWrapper.test.ts b/packages/aws/src/__tests__/ServiceWrapper.test.ts index 26e3a6c6f..b7472fe52 100644 --- a/packages/aws/src/__tests__/ServiceWrapper.test.ts +++ b/packages/aws/src/__tests__/ServiceWrapper.test.ts @@ -9,6 +9,7 @@ import AWS, { CostExplorer, Athena, S3, + Glue, } from 'aws-sdk' import { GetMetricDataInput } from 'aws-sdk/clients/cloudwatch' import { ServiceWrapper } from '../lib/ServiceWrapper' @@ -33,6 +34,7 @@ describe('aws service helper', () => { new CostExplorer(), new S3(), new Athena(), + new Glue(), ) it('enablePagination decorator should follow CostExplorer CostAndUsage next pages', async () => { @@ -206,6 +208,38 @@ describe('aws service helper', () => { expect(responses).toEqual([firstPageResponse, secondPageResponse]) }) + + it('should query AWS Glue Data Catalog for the description of a given Athena table ', async () => { + const glueGetTableSpy = jest.fn() + const mockTableDetails = { + Table: { + StorageDescriptor: { + Columns: [ + { + Name: 'column1', + Type: 'string', + }, + { + Name: 'column2', + Type: 'string', + }, + ], + }, + }, + } + glueGetTableSpy.mockResolvedValueOnce(mockTableDetails) + AWSMock.mock('Glue', 'getTable', glueGetTableSpy) + + const params = { + DatabaseName: 'database-name', + Name: 'table-name', + } + + const result = await getServiceWrapper().getAthenaTableDescription(params) + + expect(glueGetTableSpy).toHaveBeenCalledWith(params, expect.anything()) + expect(result).toEqual(mockTableDetails) + }) }) function buildAwsCostExplorerGetRightsizingRecommendationsRequest(): CostExplorer.Types.GetRightsizingRecommendationRequest { diff --git a/packages/aws/src/lib/ServiceWrapper.ts b/packages/aws/src/lib/ServiceWrapper.ts index 5d02e97f2..122dce8b5 100644 --- a/packages/aws/src/lib/ServiceWrapper.ts +++ b/packages/aws/src/lib/ServiceWrapper.ts @@ -2,7 +2,14 @@ * © 2021 Thoughtworks, Inc. */ -import { Athena, CloudWatch, CloudWatchLogs, CostExplorer, S3 } from 'aws-sdk' +import { + Athena, + CloudWatch, + CloudWatchLogs, + CostExplorer, + Glue, + S3, +} from 'aws-sdk' import csv from 'csvtojson' import { path } from 'ramda' import { @@ -27,6 +34,7 @@ export class ServiceWrapper { private readonly costExplorer: CostExplorer, private readonly s3: S3, private readonly athena?: Athena, + private readonly glue?: Glue, ) {} private async getCostAndUsageResponse( @@ -170,6 +178,12 @@ export class ServiceWrapper { const parsedCsv = await csv().fromStream(stream) return JSON.parse(JSON.stringify(parsedCsv)) } + + public async getAthenaTableDescription( + params: Glue.Types.GetTableRequest, + ): Promise { + return await this.glue.getTable(params).promise() + } } function enablePagination(nextPageProperty: string) { From 42beda60ed91650fe7e206711302803b1703ac81 Mon Sep 17 00:00:00 2001 From: Arik Smith Date: Thu, 6 Jul 2023 12:28:32 -0400 Subject: [PATCH 167/251] [#1170] adds check for cpu column in Athena table before including it in the query --- packages/aws/src/__tests__/AWSAccount.test.ts | 2 + .../src/__tests__/CostAndUsageReports.test.ts | 171 +++++++++++++++++- packages/aws/src/application/AWSAccount.ts | 2 + packages/aws/src/lib/CostAndUsageReports.ts | 54 +++++- 4 files changed, 220 insertions(+), 9 deletions(-) diff --git a/packages/aws/src/__tests__/AWSAccount.test.ts b/packages/aws/src/__tests__/AWSAccount.test.ts index b353a69e0..8e3f39f48 100644 --- a/packages/aws/src/__tests__/AWSAccount.test.ts +++ b/packages/aws/src/__tests__/AWSAccount.test.ts @@ -41,6 +41,7 @@ describe('AWSAccount', () => { const CloudWatchLogs = jest.fn() const Athena = jest.fn() const S3Service = jest.fn() + const GlueService = jest.fn() let expectedCredentials: Credentials beforeEach(() => { @@ -51,6 +52,7 @@ describe('AWSAccount', () => { CloudWatchLogs: CloudWatchLogs, Athena: Athena, S3: S3Service, + Glue: GlueService, } }) diff --git a/packages/aws/src/__tests__/CostAndUsageReports.test.ts b/packages/aws/src/__tests__/CostAndUsageReports.test.ts index ed85fa583..2234a5ac2 100644 --- a/packages/aws/src/__tests__/CostAndUsageReports.test.ts +++ b/packages/aws/src/__tests__/CostAndUsageReports.test.ts @@ -9,6 +9,7 @@ import AWS, { CostExplorer, Athena as AWSAthena, S3, + Glue, } from 'aws-sdk' import { GetQueryExecutionOutput, @@ -17,6 +18,7 @@ import { import { EstimationResult, GroupBy, + Logger, LookupTableInput, LookupTableOutput, } from '@cloud-carbon-footprint/common' @@ -95,14 +97,30 @@ describe('CostAndUsageReports Service', () => { const getQueryExecutionFailedResponse = { QueryExecution: { Status: { State: 'FAILED', StateChangeReason: 'TEST' } }, } - const getServiceWrapper = () => - new ServiceWrapper( + const getServiceWrapper = () => { + const serviceWrapper = new ServiceWrapper( new CloudWatch(), new CloudWatchLogs(), new CostExplorer(), new S3(), new AWSAthena(), + new Glue(), ) + // Ensures that tests pass product_vcpu column check by default + serviceWrapper.getAthenaTableDescription = jest.fn().mockResolvedValue({ + Table: { + StorageDescriptor: { + Columns: [ + { + Name: 'product_vcpu', + Type: 'string', + }, + ], + }, + }, + }) + return serviceWrapper + } beforeAll(() => { AWSMock.setSDKInstance(AWS) @@ -2405,6 +2423,155 @@ describe('CostAndUsageReports Service', () => { expect(result).toEqual(expectedResult) }) + describe('optional athena columns', () => { + let athenaService + beforeEach(() => { + mockStartQueryExecution(startQueryExecutionResponse) + mockGetQueryExecution(getQueryExecutionResponse) + mockGetQueryResults(athenaMockGetQueryResultsWithNoUsageAmount) + }) + + afterEach(() => { + AWSMock.restore() + jest.restoreAllMocks() + startQueryExecutionSpy.mockClear() + getQueryExecutionSpy.mockClear() + getQueryResultsSpy.mockClear() + }) + + it('validates presence of product_vcpu column before including in query', async () => { + const mockGetAthenaTable = jest.fn().mockResolvedValue({ + Table: { + StorageDescriptor: { + Columns: [ + { + Name: 'column1', + Type: 'string', + }, + { + Name: 'product_vcpu', + Type: 'string', + }, + ], + }, + }, + }) + const serviceWrapper = getServiceWrapper() + serviceWrapper.getAthenaTableDescription = mockGetAthenaTable + + athenaService = new CostAndUsageReports( + new ComputeEstimator(), + new StorageEstimator(AWS_CLOUD_CONSTANTS.SSDCOEFFICIENT), + new StorageEstimator(AWS_CLOUD_CONSTANTS.HDDCOEFFICIENT), + new NetworkingEstimator(AWS_CLOUD_CONSTANTS.NETWORKING_COEFFICIENT), + new MemoryEstimator(AWS_CLOUD_CONSTANTS.MEMORY_COEFFICIENT), + new UnknownEstimator(AWS_CLOUD_CONSTANTS.ESTIMATE_UNKNOWN_USAGE_BY), + new EmbodiedEmissionsEstimator( + AWS_CLOUD_CONSTANTS.SERVER_EXPECTED_LIFESPAN, + ), + serviceWrapper, + ) + + await athenaService.getEstimates(startDate, endDate, grouping) + + expect(mockGetAthenaTable).toHaveBeenCalledWith({ + DatabaseName: 'test-db', + Name: 'test-table', + }) + expect(startQueryExecutionSpy).toHaveBeenCalledWith( + expect.objectContaining({ + QueryString: expect.stringContaining('product_vcpu'), + }), + expect.anything(), + ) + }) + + it('excludes product_vcpu column in query if not present in athena table', async () => { + const mockGetAthenaTable = jest.fn().mockResolvedValue({ + Table: { + StorageDescriptor: { + Columns: [ + { + Name: 'column1', + Type: 'string', + }, + ], + }, + }, + }) + const serviceWrapper = getServiceWrapper() + serviceWrapper.getAthenaTableDescription = mockGetAthenaTable + + athenaService = new CostAndUsageReports( + new ComputeEstimator(), + new StorageEstimator(AWS_CLOUD_CONSTANTS.SSDCOEFFICIENT), + new StorageEstimator(AWS_CLOUD_CONSTANTS.HDDCOEFFICIENT), + new NetworkingEstimator(AWS_CLOUD_CONSTANTS.NETWORKING_COEFFICIENT), + new MemoryEstimator(AWS_CLOUD_CONSTANTS.MEMORY_COEFFICIENT), + new UnknownEstimator(AWS_CLOUD_CONSTANTS.ESTIMATE_UNKNOWN_USAGE_BY), + new EmbodiedEmissionsEstimator( + AWS_CLOUD_CONSTANTS.SERVER_EXPECTED_LIFESPAN, + ), + serviceWrapper, + ) + + await athenaService.getEstimates(startDate, endDate, grouping) + + expect(mockGetAthenaTable).toHaveBeenCalledWith({ + DatabaseName: 'test-db', + Name: 'test-table', + }) + expect(startQueryExecutionSpy).toHaveBeenCalledWith( + expect.objectContaining({ + QueryString: expect.not.stringContaining('product_vcpu'), + }), + expect.anything(), + ) + }) + + it('handles any errors from checking the athena description by ignoring the product_vcpu column', async () => { + const mockGetAthenaTable = jest + .fn() + .mockRejectedValue( + new Error('EntityNotFoundException: Database test-db not found'), + ) + const serviceWrapper = getServiceWrapper() + serviceWrapper.getAthenaTableDescription = mockGetAthenaTable + + const errorLoggerSpy = jest.spyOn(Logger.prototype, 'error') + const warningLoggerSpy = jest.spyOn(Logger.prototype, 'warn') + + athenaService = new CostAndUsageReports( + new ComputeEstimator(), + new StorageEstimator(AWS_CLOUD_CONSTANTS.SSDCOEFFICIENT), + new StorageEstimator(AWS_CLOUD_CONSTANTS.HDDCOEFFICIENT), + new NetworkingEstimator(AWS_CLOUD_CONSTANTS.NETWORKING_COEFFICIENT), + new MemoryEstimator(AWS_CLOUD_CONSTANTS.MEMORY_COEFFICIENT), + new UnknownEstimator(AWS_CLOUD_CONSTANTS.ESTIMATE_UNKNOWN_USAGE_BY), + new EmbodiedEmissionsEstimator( + AWS_CLOUD_CONSTANTS.SERVER_EXPECTED_LIFESPAN, + ), + serviceWrapper, + ) + + await athenaService.getEstimates(startDate, endDate, grouping) + + expect(errorLoggerSpy).toHaveBeenCalledWith( + 'Error verifying schema for Athena table: "test-table"', + new Error('EntityNotFoundException: Database test-db not found'), + ) + expect(warningLoggerSpy).toHaveBeenCalledWith( + `'product_vcpu' column could not be verified in Athena table schema. This may occur if there was an error fetching the schema or when there is no historical CPU usage (i.e. EC2) for the configured account. The CPU column will be excluded from Athena Query`, + ) + expect(startQueryExecutionSpy).toHaveBeenCalledWith( + expect.objectContaining({ + QueryString: expect.not.stringContaining('product_vcpu'), + }), + expect.anything(), + ) + }) + }) + const startQueryExecutionSpy = jest.fn() const getQueryExecutionSpy = jest.fn() const getQueryResultsSpy = jest.fn() diff --git a/packages/aws/src/application/AWSAccount.ts b/packages/aws/src/application/AWSAccount.ts index 3c9c7e477..0093eefb6 100644 --- a/packages/aws/src/application/AWSAccount.ts +++ b/packages/aws/src/application/AWSAccount.ts @@ -8,6 +8,7 @@ import { CloudWatchLogs, CostExplorer, Credentials, + Glue, S3 as S3Service, } from 'aws-sdk' import { ServiceConfigurationOptions } from 'aws-sdk/lib/service' @@ -206,6 +207,7 @@ export default class AWSAccount extends CloudProviderAccount { }), new S3Service(options), new Athena(options), + new Glue(options), ) } diff --git a/packages/aws/src/lib/CostAndUsageReports.ts b/packages/aws/src/lib/CostAndUsageReports.ts index dcd2cbf06..2af26aa45 100644 --- a/packages/aws/src/lib/CostAndUsageReports.ts +++ b/packages/aws/src/lib/CostAndUsageReports.ts @@ -545,6 +545,21 @@ export default class CostAndUsageReports { ) const endDate = new Date(moment.utc(end).endOf('day') as unknown as Date) + const hasCpuColumn = await this.checkIfColumnExists('product_vcpu') + + const optionalColumns = [] + let optionalColumnSelects = '' + + // Optionally adds product_vcpu in query if it exists in the schema. If there are more optional columns, we can modify this to check and for multiple columns. + if (hasCpuColumn) { + optionalColumns.push('product_vcpu') + optionalColumnSelects += 'product_vcpu as vCpus,\n' + } else { + this.costAndUsageReportsLogger.warn( + `'product_vcpu' column could not be verified in Athena table schema. This may occur if there was an error fetching the schema or when there is no historical CPU usage (i.e. EC2) for the configured account. The CPU column will be excluded from Athena Query`, + ) + } + const tagColumnNames = tagNames.map(tagNameToAthenaColumn) const tagSelectionExpression = tagColumnNames @@ -560,18 +575,17 @@ export default class CostAndUsageReports { 'line_item_product_code', 'line_item_usage_type', 'pricing_unit', - 'product_vcpu', + ...optionalColumns, ...tagColumnNames, ].join(', ') - const params = { - QueryString: `SELECT ${dateExpression} AS timestamp, + const queryString = `SELECT ${dateExpression} AS timestamp, line_item_usage_account_id as accountName, product_region as region, line_item_product_code as serviceName, line_item_usage_type as usageType, pricing_unit as usageUnit, - product_vcpu as vCpus, + ${optionalColumnSelects} SUM(line_item_usage_amount) as usageAmount, SUM(line_item_blended_cost) as cost ${tagSelectionExpression} @@ -580,9 +594,12 @@ export default class CostAndUsageReports { AND line_item_usage_start_date BETWEEN from_iso8601_timestamp('${moment .utc(startDate) .toISOString()}') AND from_iso8601_timestamp('${moment - .utc(endDate) - .toISOString()}') - GROUP BY ${groupByColumnNames}`, + .utc(endDate) + .toISOString()}') + GROUP BY ${groupByColumnNames}` + + const params = { + QueryString: queryString, QueryExecutionContext: { Database: this.dataBaseName, }, @@ -747,6 +764,29 @@ export default class CostAndUsageReports { tags, ) } + + /** + * Uses AWS Glue to get the schema of the Athena table and check if a given column is present + * @param columnName The name of the column to check (i.e. 'product_vcpu') + * @private + */ + private async checkIfColumnExists(columnName: string): Promise { + try { + const athenaTableDescription = + await this.serviceWrapper.getAthenaTableDescription({ + DatabaseName: this.dataBaseName, + Name: this.tableName, + }) + const columns = athenaTableDescription.Table?.StorageDescriptor?.Columns + return columns?.some((column) => column.Name === columnName) + } catch (error) { + this.costAndUsageReportsLogger.error( + `Error verifying schema for Athena table: "${this.tableName}"`, + error, + ) + return false + } + } } interface AWSAccountMap { From 7fe74989e6947671f893bbc757d4045cb5936b4f Mon Sep 17 00:00:00 2001 From: Arik Smith Date: Thu, 6 Jul 2023 12:58:16 -0400 Subject: [PATCH 168/251] [#1170] changeset: Fixes Athena query column error for accounts without EC2 hours/usage --- .changeset/fuzzy-insects-develop.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/fuzzy-insects-develop.md diff --git a/.changeset/fuzzy-insects-develop.md b/.changeset/fuzzy-insects-develop.md new file mode 100644 index 000000000..6b2edbff0 --- /dev/null +++ b/.changeset/fuzzy-insects-develop.md @@ -0,0 +1,5 @@ +--- +'@cloud-carbon-footprint/aws': patch +--- + +Fixes Athena query column error for accounts without EC2 hours/usage From 4c7d5373246f443881be03bcda947ff663f7481d Mon Sep 17 00:00:00 2001 From: Arik Smith Date: Thu, 6 Jul 2023 17:50:04 -0400 Subject: [PATCH 169/251] [#1174] fixes recommendation selectors and non-rendering elements in integration tests --- packages/integration-tests/.testcaferc.json | 1 - .../integration-tests/tests/page-model.js | 61 +++++++------------ .../tests/recommendations.test.js | 45 ++++++-------- 3 files changed, 39 insertions(+), 68 deletions(-) diff --git a/packages/integration-tests/.testcaferc.json b/packages/integration-tests/.testcaferc.json index 69913dbbe..7626238ef 100644 --- a/packages/integration-tests/.testcaferc.json +++ b/packages/integration-tests/.testcaferc.json @@ -2,7 +2,6 @@ "clientScripts": [ { "module": "@testing-library/dom/dist/@testing-library/dom.umd.js" } ], - "src": "tests/*test.js", "browsers": ["chrome:headless"], "concurrency": 3, "selectorTimeout": 5000, diff --git a/packages/integration-tests/tests/page-model.js b/packages/integration-tests/tests/page-model.js index 25420d3ae..392137b38 100644 --- a/packages/integration-tests/tests/page-model.js +++ b/packages/integration-tests/tests/page-model.js @@ -74,21 +74,23 @@ class Page { ) this.recAccounts = Selector('span').withText('Accounts') - //forecast card components - this.lastThirtyDayTotal = Selector( - "[data-testid='forecast-card-last-thirty-day-total']", + //forecast card components (subcomponents not included due to conditional rendering) + // this.lastThirtyDayTotal = Selector( + // "[data-testid='forecast-card-last-thirty-day-total']", + // ) + // this.projectedThirtyDayTotal = Selector( + // "[data-testid='forecast-card-projected-thirty-day-total']", + // ) + // this.forecastEquivalencyCard = Selector( + // "[data-testid='forecast-equivalency-card']", + // ) + // this.treeSeedlingsGrown = Selector("[data-testid='tree-seedlings-grown']") + // this.costSavingsPerMonth = Selector( + // "[data-testid='cost-savings-per-month']", + // ) + this.forecastErrorMessage = Selector( + "[data-testid='forecast-error-message']", ) - this.projectedThirtyDayTotal = Selector( - "[data-testid='forecast-card-projected-thirty-day-total']", - ) - this.forecastEquivalencyCard = Selector( - "[data-testid='forecast-equivalency-card']", - ) - this.treeSeedlingsGrown = Selector("[data-testid='tree-seedlings-grown']") - this.costSavingsPerMonth = Selector( - "[data-testid='cost-savings-per-month']", - ) - this.errorMessage = Selector("[data-testid='forecast-error-message']") //table components this.searchInput = Selector("[data-testid='search-input']") @@ -96,33 +98,12 @@ class Page { "[data-testid='recommendations-data-grid']", ) - //units of measure - this.unitOfMeasureLastThirtyDayTotal = Selector( - "[data-testid='unit-of-measure-last-thirty-day-total']", - ) - this.unitOfMeasureProjectedThirtyDayTotal = Selector( - "[data-testid='unit-of-measure-projected-thirty-day-total']", - ) - this.co2eSavingsLastThirtyDayTotal = Selector( - "[data-testid='co2e-savings-last-thirty-day-total'", - ) - this.co2eSavingsProjectedThirtyDayTotal = Selector( - "[data-testid='co2e-savings-projected-thirty-day-total'", - ) - this.costSavingsLastThirtyDayTotal = Selector( - "[data-testid='cost-savings-last-thirty-day-total'", - ) - this.costSavingsProjectedThirtyDayTotal = Selector( - "[data-testid='cost-savings-projected-thirty-day-total'", - ) - this.co2eSavingsField = "[data-field='co2eSavings']" - this.tableSavingsColumn = Selector( - `[role='columnheader']${this.co2eSavingsField}`, - ).nth(0) - this.firstSavingsCell = Selector( - `${this.co2eSavingsField}[role='cell']`, - ).nth(0) this.toggle = Selector("[data-testid='toggle-label']") + + this.tableSavingsColumn = Selector( + '.MuiDataGrid-columnHeaderTitle', + ).withText('Potential Carbon Savings') + this.firstSavingsCell = Selector('[data-field="co2eSavings"]').nth(0) } async loadingScreen() { diff --git a/packages/integration-tests/tests/recommendations.test.js b/packages/integration-tests/tests/recommendations.test.js index 1f98efca6..358598b53 100644 --- a/packages/integration-tests/tests/recommendations.test.js +++ b/packages/integration-tests/tests/recommendations.test.js @@ -31,17 +31,18 @@ test('filter components render with correct data when app loads', async (t) => { await t.expect(page.recommendationTypes.exists).ok() }) -test('card components render with correct data when app loads', async (t) => { +test('card components render with data when app loads', async (t) => { // commented out because the error message is not always present // await t.expect(page.errorMessage.exists).ok() - await t.expect(page.lastThirtyDayTotal.exists).ok() - await t.expect(page.projectedThirtyDayTotal.exists).ok() - await t.expect(page.forecastEquivalencyCard.exists).ok() - await t.expect(page.treeSeedlingsGrown.exists).ok() - await t.expect(page.costSavingsPerMonth.exists).ok() + await t.expect(page.forecastErrorMessage.exists).ok() + // await t.expect(page.lastThirtyDayTotal.exists).ok() + // await t.expect(page.projectedThirtyDayTotal.exists).ok() + // await t.expect(page.forecastEquivalencyCard.exists).ok() + // await t.expect(page.treeSeedlingsGrown.exists).ok() + // await t.expect(page.costSavingsPerMonth.exists).ok() }) -test('table components render with correct data when app loads', async (t) => { +test('table components renders with data when app loads', async (t) => { await t.expect(page.searchInput.exists).ok() await t.expect(page.recommendationsDataGrid.exists).ok() }) @@ -51,16 +52,6 @@ test('toggle changes unit of measure', async (t) => { // commented out because the error message is not always present // await t.expect(page.errorMessage.exists).ok() - await t - .expect(page.unitOfMeasureLastThirtyDayTotal.textContent) - .eql('Metric Tons CO2e') - await t - .expect(page.unitOfMeasureProjectedThirtyDayTotal.textContent) - .eql('Metric Tons CO2e') - await t.expect(page.co2eSavingsLastThirtyDayTotal.exists).ok() - await t.expect(page.co2eSavingsProjectedThirtyDayTotal.exists).ok() - await t.expect(page.costSavingsLastThirtyDayTotal.exists).ok() - await t.expect(page.costSavingsProjectedThirtyDayTotal.exists).ok() //check first cell await t .expect(page.tableSavingsColumn.textContent) @@ -69,16 +60,16 @@ test('toggle changes unit of measure', async (t) => { //click kilogram toggle await t.click(page.toggle, { isTrusted: true }) //recheck data in - kg instead of metric tons, so 1000 - await t - .expect(page.unitOfMeasureLastThirtyDayTotal.textContent) - .eql('Kilograms CO2e') - await t - .expect(page.unitOfMeasureProjectedThirtyDayTotal.textContent) - .eql('Kilograms CO2e') - await t.expect(page.co2eSavingsLastThirtyDayTotal.exists).ok() - await t.expect(page.co2eSavingsProjectedThirtyDayTotal.exists).ok() - await t.expect(page.costSavingsLastThirtyDayTotal.exists).ok() - await t.expect(page.costSavingsProjectedThirtyDayTotal.exists).ok() + // await t + // .expect(page.unitOfMeasureLastThirtyDayTotal.textContent) + // .eql('Kilograms CO2e') + // await t + // .expect(page.unitOfMeasureProjectedThirtyDayTotal.textContent) + // .eql('Kilograms CO2e') + // await t.expect(page.co2eSavingsLastThirtyDayTotal.exists).ok() + // await t.expect(page.co2eSavingsProjectedThirtyDayTotal.exists).ok() + // await t.expect(page.costSavingsLastThirtyDayTotal.exists).ok() + // await t.expect(page.costSavingsProjectedThirtyDayTotal.exists).ok() await t .expect(page.tableSavingsColumn.textContent) .eql('Potential Carbon Savings (kg)') From 6d4ce5f21f55fc396051c201c9716b7e013ccecd Mon Sep 17 00:00:00 2001 From: Arik Smith Date: Thu, 6 Jul 2023 17:54:50 -0400 Subject: [PATCH 170/251] changeset: Fixes failing recommendations test --- .changeset/ten-pets-buy.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/ten-pets-buy.md diff --git a/.changeset/ten-pets-buy.md b/.changeset/ten-pets-buy.md new file mode 100644 index 000000000..b473e229c --- /dev/null +++ b/.changeset/ten-pets-buy.md @@ -0,0 +1,5 @@ +--- +'@cloud-carbon-footprint/integration-tests': patch +--- + +Fixes failing recommendations test From 3413cea9df18f4a81bc26635431beace3709ce10 Mon Sep 17 00:00:00 2001 From: jianglinwang Date: Mon, 10 Jul 2023 16:36:09 +0800 Subject: [PATCH 171/251] refactor code. --- .talismanrc | 2 +- packages/ali/README.md | 2 +- packages/ali/src/__tests__/AliAccount.test.ts | 41 ------------- .../__tests__/AliCostAndUsageService.test.ts | 1 + packages/ali/src/application/AliAccount.ts | 11 ---- packages/ali/src/lib/AliCalculateRow.ts | 61 ++++--------------- .../ali/src/lib/AliCostAndUsageService.ts | 2 +- packages/api/.env.template | 5 +- packages/common/src/Config.ts | 19 +----- .../src/helpers/__tests__/helpers.test.ts | 13 +++- packages/common/src/helpers/helpers.ts | 8 +++ packages/common/src/helpers/index.ts | 1 + yarn.lock | 2 +- 13 files changed, 42 insertions(+), 126 deletions(-) delete mode 100644 packages/ali/src/__tests__/AliAccount.test.ts diff --git a/.talismanrc b/.talismanrc index 379dcc273..b3c7df94d 100644 --- a/.talismanrc +++ b/.talismanrc @@ -120,7 +120,7 @@ fileignoreconfig: - filename: packages/ali/src/lib/AliCalculateRow.ts checksum: 77997e1e88c3fe9bbafbda63d4c1c9b83df439417664cf0d080488b01c8c0c51 - filename: packages/api/.env.template - checksum: b9441ffab35ccf38716f30ef928830197ab87aa3a8693028a73caa53d6763e6d + checksum: 024d92688cbbca7a345f9c9ee53e86d377ec6df92ddb746dce90c57b2a5c988f - filename: packages/api/CHANGELOG.md checksum: 70538ae254f51f772d64e42ddb9f2c6393fc49969ea0be0cc5c91a6447204032 - filename: packages/api/create_docker_secrets.sh diff --git a/packages/ali/README.md b/packages/ali/README.md index 8736bee60..a0f9e6db6 100644 --- a/packages/ali/README.md +++ b/packages/ali/README.md @@ -1,6 +1,6 @@ # @cloud-carbon-footprint/ali -This package provides the core logic to get cloud usage data and estimate energy and carbon emissions from Amazon Web Services. +This package provides the core logic to get cloud usage data and estimate energy and carbon emissions from Aliyun (Alibaba Cloud). ## Installation diff --git a/packages/ali/src/__tests__/AliAccount.test.ts b/packages/ali/src/__tests__/AliAccount.test.ts deleted file mode 100644 index 8b9d302b3..000000000 --- a/packages/ali/src/__tests__/AliAccount.test.ts +++ /dev/null @@ -1,41 +0,0 @@ -/* - * © 2023 Thoughtworks, Inc. - */ - -import { GroupBy } from '@cloud-carbon-footprint/common' - -import AliAccount from '../application/AliAccount' - -describe('Ali Account', () => { - const startDate: Date = new Date('2021-01-01') - const endDate: Date = new Date('2021-01-01') - const grouping: GroupBy = GroupBy.day - - it('gets results from getDataForRegions function', async () => { - const aliAccount = new AliAccount() - const results = await aliAccount.getDataForRegions( - startDate, - endDate, - grouping, - ) - - expect(results).toEqual(null) - }) - - // it('gets results from getDataFromCostAndUsageReports function', async () => { - // const aliAccount = new AliAccount() - // const results = await aliAccount.getDataFromCostAndUsageReports( - // startDate, - // endDate, - // grouping, - // ) - // - // expect(results).toEqual([ - // { - // groupBy: 'day', - // serviceEstimates: [], - // timestamp: '2021-01-01T00:00:00.000Z', - // }, - // ]) - // }) -}) diff --git a/packages/ali/src/__tests__/AliCostAndUsageService.test.ts b/packages/ali/src/__tests__/AliCostAndUsageService.test.ts index 0864040d5..b0aa60dc4 100644 --- a/packages/ali/src/__tests__/AliCostAndUsageService.test.ts +++ b/packages/ali/src/__tests__/AliCostAndUsageService.test.ts @@ -30,6 +30,7 @@ const getUsageSpy = jest.spyOn(AliCostAndUsageService.prototype, 'getUsage') const DEFAULT_CONFIG: CCFConfig = { ALI: { + NAME: 'AliCloud', authentication: { accessKeyId: 'id', accessKeySecret: 'secret', diff --git a/packages/ali/src/application/AliAccount.ts b/packages/ali/src/application/AliAccount.ts index eaaa5b8ce..17d3fe7d5 100644 --- a/packages/ali/src/application/AliAccount.ts +++ b/packages/ali/src/application/AliAccount.ts @@ -28,17 +28,6 @@ export default class AliAccount extends CloudProviderAccount { this.logger = new Logger('ALIAccount') } - async getDataForRegions( - startDate: Date, - endDate: Date, - grouping: GroupBy, - ): Promise { - this.logger.info( - `startDate: ${startDate}, endDate: ${endDate}, grouping: ${grouping}`, - ) - return null - } - getDataFromCostAndUsageReports( startDate: Date, endDate: Date, diff --git a/packages/ali/src/lib/AliCalculateRow.ts b/packages/ali/src/lib/AliCalculateRow.ts index 2afb2e74a..b771ff29f 100644 --- a/packages/ali/src/lib/AliCalculateRow.ts +++ b/packages/ali/src/lib/AliCalculateRow.ts @@ -1,7 +1,7 @@ /* * © 2023 Thoughtworks, Inc. */ - +import { mapToArabic } from '@cloud-carbon-footprint/common' import { BillingDataRow } from '@cloud-carbon-footprint/core' import { DescribeInstanceBillResponseBodyDataItems } from '@alicloud/bssopenapi20171214/src/client' import { ALI_REPLICATION_FACTORS_FOR_SERVICES } from './ReplicationFactors' @@ -70,13 +70,7 @@ export default class AliCalculateRow extends BillingDataRow { return 0 } else { const quantity = parseInt(resultArr[0]) - let unitValue = 0 - const unit = resultArr[1] - if (unit == '万') { - unitValue = 10000 - } else { - unitValue = 100000000 - } + const unitValue = mapToArabic(resultArr[1]) return (quantity * unitValue) / 3600 } } @@ -108,13 +102,7 @@ export default class AliCalculateRow extends BillingDataRow { return 0 } else { const quantity = parseInt(resultArr[0]) - let unitValue = 0 - const unit = resultArr[1] - if (unit == '万') { - unitValue = 10000 - } else { - unitValue = 100000000 - } + const unitValue = mapToArabic(resultArr[1]) return (quantity * unitValue) / 3600 } } @@ -144,41 +132,16 @@ export default class AliCalculateRow extends BillingDataRow { } private getUsage(servicePeriod: string, servicePeriodUnit: string) { - let ratio = 1 - switch (servicePeriodUnit) { - case '秒': - { - ratio = 1 / 3600 - } - break - case '分': - { - ratio = 1 / 60 - } - break - case '时': - { - ratio = 1 - } - break - case '天': - { - ratio = 24 - } - break - case '月': - { - ratio = 24 * 30 - } - break - case '年': - { - ratio = 24 * 30 * 12 - } - break - default: - break + const servicePeriodUnitRatios = { + 秒: 1 / 3600, + 分: 1 / 60, + 时: 1, + 天: 24, + 月: 24 * 30, + 年: 24 * 30 * 12, } + + const ratio = servicePeriodUnitRatios[servicePeriodUnit] || 1 return parseInt(servicePeriod) * ratio } diff --git a/packages/ali/src/lib/AliCostAndUsageService.ts b/packages/ali/src/lib/AliCostAndUsageService.ts index 5079f4ea3..e6697ea91 100644 --- a/packages/ali/src/lib/AliCostAndUsageService.ts +++ b/packages/ali/src/lib/AliCostAndUsageService.ts @@ -127,7 +127,7 @@ export default class AliCostAndUsageService { ) this.logger.info('row:' + JSON.stringify(row)) serviceEstimates.push({ - cloudProvider: 'AliCloud', + cloudProvider: aliConfig.NAME, accountName: row.accountName, serviceName: row.serviceName, accountId: row.accountId, diff --git a/packages/api/.env.template b/packages/api/.env.template index f1a992e9f..9eb6047dc 100644 --- a/packages/api/.env.template +++ b/packages/api/.env.template @@ -75,8 +75,9 @@ AZURE_SUBSCRIPTION_CHUNKS=0 AZURE_SUBSCRIPTIONS=["subscription-1", "subscription-2"] # Ali -ALI_INCLUDE_ESTIMATES=true -ALI_USE_BILLING_DATA=true + +ALI_ACCESS_KEY=your-alicloud-access-key +ALI_ACCESS_SECRET=your-alicloud-access-secret # Cache diff --git a/packages/common/src/Config.ts b/packages/common/src/Config.ts index a2cddb04a..d2e30331c 100644 --- a/packages/common/src/Config.ts +++ b/packages/common/src/Config.ts @@ -65,10 +65,6 @@ export interface CCFConfig { } ALI?: { NAME?: string - CURRENT_SERVICES?: { key: string; name: string }[] - CURRENT_REGIONS?: string[] - INCLUDE_ESTIMATES?: boolean - USE_BILLING_DATA?: boolean authentication?: { accessKeyId: string accessKeySecret: string @@ -289,20 +285,7 @@ const getConfig = (): CCFConfig => ({ SUBSCRIPTIONS: JSON.parse(getAzureSubscriptions()) || [], }, ALI: { - NAME: 'ALI', - CURRENT_REGIONS: ['us-east1', 'us-central1', 'us-west1'], - CURRENT_SERVICES: [ - { - key: 'computeEngine', - name: 'ComputeEngine', - }, - ], - INCLUDE_ESTIMATES: process.env.ALI_INCLUDE_ESTIMATES - ? !!process.env.ALI_INCLUDE_ESTIMATES - : true, - USE_BILLING_DATA: - !!process.env.ALI_USE_BILLING_DATA && - process.env.ALI_USE_BILLING_DATA !== 'false', + NAME: 'AliCloud', authentication: { accessKeyId: process.env.ALI_ACCESS_KEY, accessKeySecret: process.env.ALI_ACCESS_SECRET, diff --git a/packages/common/src/helpers/__tests__/helpers.test.ts b/packages/common/src/helpers/__tests__/helpers.test.ts index f992059b2..166b40285 100644 --- a/packages/common/src/helpers/__tests__/helpers.test.ts +++ b/packages/common/src/helpers/__tests__/helpers.test.ts @@ -8,8 +8,9 @@ import { containsAny, endsWithAny, getHoursInMonth, - wait, getPeriodEndDate, + mapToArabic, + wait, } from '../helpers' jest.useFakeTimers() @@ -78,4 +79,14 @@ describe('Helpers', () => { expect(result).toEqual(expectedResult) }) }) + + describe('converts chinese to arabic', () => { + each([ + ['万', 10000], + ['亿', 100000000], + ]).it('should get period end date for %s', (unit, expectedResult) => { + const result = mapToArabic(unit) + expect(result).toEqual(expectedResult) + }) + }) }) diff --git a/packages/common/src/helpers/helpers.ts b/packages/common/src/helpers/helpers.ts index 30076d758..f078ec995 100644 --- a/packages/common/src/helpers/helpers.ts +++ b/packages/common/src/helpers/helpers.ts @@ -44,3 +44,11 @@ export const getPeriodEndDate = (startDate: Date, grouping: GroupBy): Date => { return periodGrouping[grouping] } + +export const mapToArabic = (unit: string): number => { + if (unit == '万') { + return 10000 + } else { + return 100000000 + } +} diff --git a/packages/common/src/helpers/index.ts b/packages/common/src/helpers/index.ts index 22a1eb138..27fe2da18 100644 --- a/packages/common/src/helpers/index.ts +++ b/packages/common/src/helpers/index.ts @@ -20,4 +20,5 @@ export { wait, getHoursInMonth, getPeriodEndDate, + mapToArabic, } from './helpers' diff --git a/yarn.lock b/yarn.lock index 9fb466335..f898e8f82 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2641,7 +2641,7 @@ __metadata: languageName: unknown linkType: soft -"@cloud-carbon-footprint/common@^1.10.0, @cloud-carbon-footprint/common@^1.12.0, @cloud-carbon-footprint/common@^1.8.0, @cloud-carbon-footprint/common@workspace:packages/common": +"@cloud-carbon-footprint/common@^1.10.0, @cloud-carbon-footprint/common@^1.12.0, @cloud-carbon-footprint/common@^1.8.0, @cloud-carbon-footprint/common@^1.9.0, @cloud-carbon-footprint/common@workspace:packages/common": version: 0.0.0-use.local resolution: "@cloud-carbon-footprint/common@workspace:packages/common" dependencies: From 84184750cc4f87ec7c499201aafcc5b84e72ec0d Mon Sep 17 00:00:00 2001 From: Marco Valtas Date: Mon, 10 Jul 2023 09:26:56 -0400 Subject: [PATCH 172/251] Fix docker secrets to account for multiple '=' in conf --- .talismanrc | 2 +- packages/api/create_docker_secrets.sh | 16 +++++++--------- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/.talismanrc b/.talismanrc index e4e55cdfe..864806030 100644 --- a/.talismanrc +++ b/.talismanrc @@ -116,7 +116,7 @@ fileignoreconfig: - filename: packages/api/.env.template checksum: ec94dc4f50d599e64405562c74b0ba2d1f49781dba1d0fbc9f5011eeea7e2101 - filename: packages/api/create_docker_secrets.sh - checksum: 6413d65edef7f51e4a0def0f8f5eb665f389e30cf19d3b02f71ae9f9cd7aa500 + checksum: 61bf83f49efc9578228e8136896e957d0f6a50d62034413f1173e41f10304b83 - filename: packages/api/create_server_env_file.sh checksum: b6a9b46936e31ca0690ea2fc7080f4c6b5eac0e807c6a71e9dabc13ff8a2d026 - filename: packages/api/estimates.cache-uscs.json diff --git a/packages/api/create_docker_secrets.sh b/packages/api/create_docker_secrets.sh index ba967c1be..80550fd60 100755 --- a/packages/api/create_docker_secrets.sh +++ b/packages/api/create_docker_secrets.sh @@ -19,7 +19,7 @@ while IFS= read -r line; do elif [[ $line == " "* ]]; then # line is a value if (( inside_secrets )); then key=$(echo "$line" | awk -F: '{print $1}' | sed 's/^[ \t]*//;s/[ \t]*$//') - [[ ${key} == "file" ]] && continue # skip 'file:' entries + [[ ${key} == "file" || ${key} =~ ^# ]] && continue # skip 'file:' entries touch $HOME/.docker/secrets/$key fi else @@ -29,12 +29,10 @@ done < ../../docker-compose.yml # Fill secrets with values from .env file while read line; do - keyValue=(${line//=/ }) - key=${keyValue[0]} - value=${keyValue[1]} - - # skip commented out variables - [[ -z $key || $key == \#* ]] && continue - - echo $value > $HOME/.docker/secrets/$key + # skip commented out variables and lines without '=' + if [[ ! ${line} =~ ^# && "${line}" == *"="* ]]; then + key=${line%%=*} + value=${line#*=} + echo ${value} > ${HOME}/.docker/secrets/${key} + fi done < .env From 3017fe9400666e4ce5ccd19d3bf238730da4ea64 Mon Sep 17 00:00:00 2001 From: Arik Smith Date: Wed, 12 Jul 2023 17:05:45 -0400 Subject: [PATCH 173/251] fix module build error with ali cloud by removing implicity any check --- packages/ali/package.json | 6 +++--- packages/ali/tsconfig.json | 7 ++++--- yarn.lock | 16 ++++++++-------- 3 files changed, 15 insertions(+), 14 deletions(-) diff --git a/packages/ali/package.json b/packages/ali/package.json index 47755b61a..af120e068 100644 --- a/packages/ali/package.json +++ b/packages/ali/package.json @@ -41,9 +41,9 @@ "lint:fix": "eslint '*/**/*.ts' --quiet --fix" }, "dependencies": { - "@alicloud/bssopenapi20171214": "^1.0.12", - "@cloud-carbon-footprint/common": "^1.9.0", - "@cloud-carbon-footprint/core": "^0.17.0", + "@alicloud/bssopenapi20171214": "^1.0.14", + "@cloud-carbon-footprint/common": "^1.10.0", + "@cloud-carbon-footprint/core": "^0.17.3", "@google-cloud/iam-credentials": "^1.1.1", "csvtojson": "^2.0.10", "moment": "^2.29.1", diff --git a/packages/ali/tsconfig.json b/packages/ali/tsconfig.json index 53de7d253..b0bde9107 100644 --- a/packages/ali/tsconfig.json +++ b/packages/ali/tsconfig.json @@ -3,7 +3,8 @@ "compilerOptions": { "rootDir": "./src", "baseUrl": ".", - "outDir": "./dist" - }, - "include": ["./src"] + "outDir": "./dist", + "noImplicitAny": false // <-- Added because AliCloud SDK does not support implicit any. TODO: Find a workaround for this + }, + "include": ["./src"], } diff --git a/yarn.lock b/yarn.lock index f898e8f82..333358659 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12,16 +12,16 @@ __metadata: languageName: node linkType: hard -"@alicloud/bssopenapi20171214@npm:^1.0.12": - version: 1.0.13 - resolution: "@alicloud/bssopenapi20171214@npm:1.0.13" +"@alicloud/bssopenapi20171214@npm:^1.0.14": + version: 1.0.14 + resolution: "@alicloud/bssopenapi20171214@npm:1.0.14" dependencies: "@alicloud/endpoint-util": ^0.0.1 "@alicloud/openapi-client": ^0.4.4 "@alicloud/openapi-util": ^0.3.1 "@alicloud/tea-typescript": ^1.7.1 "@alicloud/tea-util": ^1.4.5 - checksum: fd8f76937e4cc909b962f41c6b3d4b748246719ba72984d1aa37672d2f8c028240e253c57132498e3885b9ca546559c16960b2197e1b77bead992865aeca467b + checksum: 42c7b4142d2d201fe4b92630a744db435237afdee923a1a3155c0199906f249e1551580eba72b313af1fed5f84618c928a75996ad2e68a08320cdded78596a76 languageName: node linkType: hard @@ -2352,9 +2352,9 @@ __metadata: version: 0.0.0-use.local resolution: "@cloud-carbon-footprint/ali@workspace:packages/ali" dependencies: - "@alicloud/bssopenapi20171214": ^1.0.12 - "@cloud-carbon-footprint/common": ^1.9.0 - "@cloud-carbon-footprint/core": ^0.17.0 + "@alicloud/bssopenapi20171214": ^1.0.14 + "@cloud-carbon-footprint/common": ^1.10.0 + "@cloud-carbon-footprint/core": ^0.17.3 "@google-cloud/iam-credentials": ^1.1.1 "@types/jest": ^27.4.0 "@types/jest-when": ^3.5.0 @@ -2641,7 +2641,7 @@ __metadata: languageName: unknown linkType: soft -"@cloud-carbon-footprint/common@^1.10.0, @cloud-carbon-footprint/common@^1.12.0, @cloud-carbon-footprint/common@^1.8.0, @cloud-carbon-footprint/common@^1.9.0, @cloud-carbon-footprint/common@workspace:packages/common": +"@cloud-carbon-footprint/common@^1.10.0, @cloud-carbon-footprint/common@^1.12.0, @cloud-carbon-footprint/common@^1.8.0, @cloud-carbon-footprint/common@workspace:packages/common": version: 0.0.0-use.local resolution: "@cloud-carbon-footprint/common@workspace:packages/common" dependencies: From 95896791a4a057d307a3a0467b2f7d12e33ac754 Mon Sep 17 00:00:00 2001 From: Arik Smith Date: Mon, 17 Jul 2023 15:45:14 -0400 Subject: [PATCH 174/251] adds config for disabling forecast validation, disables for demo app --- .github/workflows/demo-app.yml | 1 + packages/client/.env.template | 4 ++ packages/client/src/Config.ts | 3 + .../RecommendationsPage.test.tsx | 55 ++++++++++++++++++- .../RecommendationsPage.tsx | 9 ++- 5 files changed, 68 insertions(+), 4 deletions(-) diff --git a/.github/workflows/demo-app.yml b/.github/workflows/demo-app.yml index 7018545f4..ecbe1c490 100644 --- a/.github/workflows/demo-app.yml +++ b/.github/workflows/demo-app.yml @@ -39,6 +39,7 @@ jobs: REACT_APP_PREVIOUS_YEAR_OF_USAGE: ${{ secrets.REACT_APP_PREVIOUS_YEAR_OF_USAGE }} REACT_APP_GROUP_BY: ${{ secrets.REACT_APP_GROUP_BY }} REACT_APP_MINIMAL_DATE_AGE: ${{ secrets.REACT_APP_MINIMAL_DATE_AGE }} + REACT_APP_DISABLE_FORECAST_VALIDATION: ${{ secrets.REACT_APP_DISABLE_FORECAST_VALIDATION }} run: | cd packages/client ./create_client_env_file.sh diff --git a/packages/client/.env.template b/packages/client/.env.template index c418ea020..59b356fc7 100644 --- a/packages/client/.env.template +++ b/packages/client/.env.template @@ -22,6 +22,10 @@ REACT_APP_GROUP_BY=day # General cache config - Optionally disable and ignore the cache and request fresh estimates each time (true/false) REACT_APP_DISABLE_CACHE=true +# Forecast validation config - Optionally disable the forecast validation and use the forecasted data as is (true/false) +# [For accurate forecasts, it is recommended to keep this enabled] +REACT_APP_DISABLE_FORECAST_VALIDATION=true + # MongoDB config # When using MongoDB as cache, large results will be paginated and split into multiple requests. Use this to set a limit for results per page/request. PAGE_LIMIT=30 diff --git a/packages/client/src/Config.ts b/packages/client/src/Config.ts index 80cc36675..a11effd99 100644 --- a/packages/client/src/Config.ts +++ b/packages/client/src/Config.ts @@ -19,6 +19,7 @@ export interface ClientConfig { START_DATE: string END_DATE: string DISABLE_CACHE: boolean + DISABLE_FORECAST_VALIDATION: boolean TEST_MODE: boolean } @@ -60,6 +61,8 @@ const appConfig: ClientConfig = { START_DATE: process.env.REACT_APP_START_DATE, END_DATE: endDate, DISABLE_CACHE: process.env.REACT_APP_DISABLE_CACHE === 'true', + DISABLE_FORECAST_VALIDATION: + process.env.REACT_APP_DISABLE_FORECAST_VALIDATION === 'true', TEST_MODE: process.env.REACT_APP_TEST_MODE === 'true', } diff --git a/packages/client/src/pages/RecommendationsPage/RecommendationsPage.test.tsx b/packages/client/src/pages/RecommendationsPage/RecommendationsPage.test.tsx index 03a4ba4c7..d983bbe58 100644 --- a/packages/client/src/pages/RecommendationsPage/RecommendationsPage.test.tsx +++ b/packages/client/src/pages/RecommendationsPage/RecommendationsPage.test.tsx @@ -12,13 +12,15 @@ import { import RecommendationsPage from './RecommendationsPage' import { generateEstimations, mockRecommendationData } from '../../utils/data' import { useRemoteRecommendationsService } from '../../utils/hooks' +import configLoader from '../../ConfigLoader' import { ServiceResult } from '../../Types' +import * as helpers from '../../utils/helpers/handleDates' jest.mock('../../utils/hooks/RecommendationsServiceHook') jest.mock('../../utils/hooks/FootprintServiceHook') jest.mock('../../ConfigLoader', () => ({ __esModule: true, - default: () => ({ + default: jest.fn().mockImplementation(() => ({ CURRENT_PROVIDERS: [ { key: 'aws', name: 'AWS' }, { key: 'gcp', name: 'GCP' }, @@ -29,7 +31,8 @@ jest.mock('../../ConfigLoader', () => ({ TYPE: 'days', }, BASE_URL: '/api', - }), + GROUP_BY: 'day', + })), })) const mockedUseRecommendationsService = @@ -57,10 +60,24 @@ describe('Recommendations Page', () => { mockedUseRecommendationsService.mockReturnValue( mockRecommendationsReturnValue, ) + ;(configLoader as jest.Mock).mockImplementation(() => ({ + CURRENT_PROVIDERS: [ + { key: 'aws', name: 'AWS' }, + { key: 'gcp', name: 'GCP' }, + ], + PREVIOUS_YEAR_OF_USAGE: true, + DATE_RANGE: { + VALUE: '7', + TYPE: 'days', + }, + BASE_URL: '/api', + GROUP_BY: 'day', + })) }) afterEach(() => { mockedUseRecommendationsService.mockClear() + jest.clearAllMocks() }) it('renders an error message for forecast when missing emissions data', () => { @@ -126,4 +143,38 @@ describe('Recommendations Page', () => { expect(getByTestId('toggle')).toBeInTheDocument() }) + + it('skips checking for missing dates if forecast validation is disabled', () => { + ;(configLoader as jest.Mock).mockImplementation(() => ({ + CURRENT_PROVIDERS: [ + { key: 'aws', name: 'AWS' }, + { key: 'gcp', name: 'GCP' }, + ], + PREVIOUS_YEAR_OF_USAGE: true, + DATE_RANGE: { + VALUE: '7', + TYPE: 'days', + }, + BASE_URL: '/api', + DISABLE_FORECAST_VALIDATION: 'true', + GROUP_BY: 'day', + })) + + const mockSmallFootprintData: ServiceResult = { + loading: false, + data: generateEstimations(moment.utc(), 1), + error: null, + } + + const checkDatesSpy = jest.spyOn(helpers, 'checkFootprintDates') + + render( + , + ) + + expect(checkDatesSpy).not.toHaveBeenCalled() + }) }) diff --git a/packages/client/src/pages/RecommendationsPage/RecommendationsPage.tsx b/packages/client/src/pages/RecommendationsPage/RecommendationsPage.tsx index 0e8ea60c8..4349dbb7b 100644 --- a/packages/client/src/pages/RecommendationsPage/RecommendationsPage.tsx +++ b/packages/client/src/pages/RecommendationsPage/RecommendationsPage.tsx @@ -17,7 +17,7 @@ import { FootprintData } from '../../utils/hooks' import { checkFootprintDates, sliceFootprintDataByLastMonth, -} from '../../utils/helpers/handleDates' +} from '../../utils/helpers' interface RecommendationsPageProps { onApiError?: (e: ErrorState) => void @@ -35,9 +35,14 @@ const RecommendationsPage = ({ const [co2eUnit, setCo2eUnit] = useState(Co2eUnit.MetricTonnes) const groupBy = config.GROUP_BY + const hasForecastValidationDisabled = config.DISABLE_FORECAST_VALIDATION + + let forecastDetails = { missingDates: [], groupBy } const slicedFootprint = sliceFootprintDataByLastMonth(footprint.data, groupBy) - const forecastDetails = checkFootprintDates(slicedFootprint, groupBy) + if (!hasForecastValidationDisabled) { + forecastDetails = checkFootprintDates(slicedFootprint, groupBy) + } const recommendations = useRecommendationData({ baseUrl: config.BASE_URL, From 5a8ce222d3b4e0fed3182dc98798a34d22e056dc Mon Sep 17 00:00:00 2001 From: Arik Smith Date: Mon, 17 Jul 2023 15:52:05 -0400 Subject: [PATCH 175/251] temporarily enable forecast in integration tests --- packages/integration-tests/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/integration-tests/package.json b/packages/integration-tests/package.json index 8e132e045..c83074821 100644 --- a/packages/integration-tests/package.json +++ b/packages/integration-tests/package.json @@ -5,7 +5,7 @@ "description": "Test repository to run integration tests", "scripts": { "start": "concurrently \"yarn start-client\" \"yarn start-api\"", - "start-client": "BROWSER=none REACT_APP_TEST_MODE=true yarn workspace @cloud-carbon-footprint/client start", + "start-client": "BROWSER=none REACT_APP_TEST_MODE=true REACT_APP_DISABLE_FORECAST_VALIDATION=false yarn workspace @cloud-carbon-footprint/client start", "start-api": "TEST_MODE=true CACHE_MODE=LOCAL ENABLE_CORS=false yarn workspace @cloud-carbon-footprint/api start", "headless": "testcafe 'chromium:headless' 'tests/*test.js'", "headed": "testcafe 'chrome'", From 8b287565fac9f97b1e2ea1c2f7f443f99066b145 Mon Sep 17 00:00:00 2001 From: Arik Smith Date: Mon, 17 Jul 2023 15:58:51 -0400 Subject: [PATCH 176/251] update templates --- .../default-app/packages/client/src/Config.ts | 4 ++++ .../templates/default-app/packages/client/src/lib.ts | 7 +++++++ .../pages/RecommendationsPage/RecommendationsPage.tsx | 11 ++++++++--- .../packages/client/src/utils/helpers/handleDates.ts | 8 ++++++-- .../client/src/utils/helpers/transformData.ts | 8 +++----- .../src/utils/hooks/RecommendationsServiceHook.ts | 1 - 6 files changed, 28 insertions(+), 11 deletions(-) diff --git a/packages/create-app/templates/default-app/packages/client/src/Config.ts b/packages/create-app/templates/default-app/packages/client/src/Config.ts index b2cacaaf9..a11effd99 100644 --- a/packages/create-app/templates/default-app/packages/client/src/Config.ts +++ b/packages/create-app/templates/default-app/packages/client/src/Config.ts @@ -19,6 +19,7 @@ export interface ClientConfig { START_DATE: string END_DATE: string DISABLE_CACHE: boolean + DISABLE_FORECAST_VALIDATION: boolean TEST_MODE: boolean } @@ -46,6 +47,7 @@ const appConfig: ClientConfig = { { key: 'aws', name: 'AWS' }, { key: 'gcp', name: 'GCP' }, { key: 'azure', name: 'Azure' }, + { key: 'alicloud', name: 'AliCloud' }, ], PREVIOUS_YEAR_OF_USAGE: previousYearOfUsage, DATE_RANGE: { @@ -59,6 +61,8 @@ const appConfig: ClientConfig = { START_DATE: process.env.REACT_APP_START_DATE, END_DATE: endDate, DISABLE_CACHE: process.env.REACT_APP_DISABLE_CACHE === 'true', + DISABLE_FORECAST_VALIDATION: + process.env.REACT_APP_DISABLE_FORECAST_VALIDATION === 'true', TEST_MODE: process.env.REACT_APP_TEST_MODE === 'true', } diff --git a/packages/create-app/templates/default-app/packages/client/src/lib.ts b/packages/create-app/templates/default-app/packages/client/src/lib.ts index d1e6b40e1..82b988e2d 100644 --- a/packages/create-app/templates/default-app/packages/client/src/lib.ts +++ b/packages/create-app/templates/default-app/packages/client/src/lib.ts @@ -1,5 +1,11 @@ +/* + * © 2021 Thoughtworks, Inc. + */ + export { default as HeaderBar } from './layout/HeaderBar' +export { default as useFilters } from './common/FilterBar/utils/FilterHook' + export { default as RecommendationsFilterBar } from './pages/RecommendationsPage/RecommendationsFilterBar' export { default as RecommendationsSidePanel } from './pages/RecommendationsPage/RecommendationsSidePanel' export { default as RecommendationsTable } from './pages/RecommendationsPage/RecommendationsTable' @@ -16,3 +22,4 @@ export type { ClientConfig } from './Config' export * from './Types' export * from './utils/hooks' export * from './utils/themes' +export * from './utils/helpers' diff --git a/packages/create-app/templates/default-app/packages/client/src/pages/RecommendationsPage/RecommendationsPage.tsx b/packages/create-app/templates/default-app/packages/client/src/pages/RecommendationsPage/RecommendationsPage.tsx index 1dfad0e24..4349dbb7b 100644 --- a/packages/create-app/templates/default-app/packages/client/src/pages/RecommendationsPage/RecommendationsPage.tsx +++ b/packages/create-app/templates/default-app/packages/client/src/pages/RecommendationsPage/RecommendationsPage.tsx @@ -17,7 +17,7 @@ import { FootprintData } from '../../utils/hooks' import { checkFootprintDates, sliceFootprintDataByLastMonth, -} from '../../utils/helpers/handleDates' +} from '../../utils/helpers' interface RecommendationsPageProps { onApiError?: (e: ErrorState) => void @@ -35,9 +35,14 @@ const RecommendationsPage = ({ const [co2eUnit, setCo2eUnit] = useState(Co2eUnit.MetricTonnes) const groupBy = config.GROUP_BY + const hasForecastValidationDisabled = config.DISABLE_FORECAST_VALIDATION + + let forecastDetails = { missingDates: [], groupBy } const slicedFootprint = sliceFootprintDataByLastMonth(footprint.data, groupBy) - const forecastDetails = checkFootprintDates(slicedFootprint, groupBy) + if (!hasForecastValidationDisabled) { + forecastDetails = checkFootprintDates(slicedFootprint, groupBy) + } const recommendations = useRecommendationData({ baseUrl: config.BASE_URL, @@ -60,7 +65,7 @@ const RecommendationsPage = ({
0 ? '< 0.001' : formattedValue } diff --git a/packages/create-app/templates/default-app/packages/client/src/utils/hooks/RecommendationsServiceHook.ts b/packages/create-app/templates/default-app/packages/client/src/utils/hooks/RecommendationsServiceHook.ts index 5aea110e1..433640b2a 100644 --- a/packages/create-app/templates/default-app/packages/client/src/utils/hooks/RecommendationsServiceHook.ts +++ b/packages/create-app/templates/default-app/packages/client/src/utils/hooks/RecommendationsServiceHook.ts @@ -8,7 +8,6 @@ import { RecommendationResult } from '@cloud-carbon-footprint/common' import { ServiceResult } from '../../Types' import { useAxiosErrorHandling } from '../../layout/ErrorPage' import { FootprintData } from './FootprintDataHook' - export interface UseRemoteRecommendationServiceParams { baseUrl: string | null onApiError?: (e: Error) => void From dced022c37eaac0315c1ed6a52c186be854f8756 Mon Sep 17 00:00:00 2001 From: Arik Smith Date: Mon, 17 Jul 2023 16:54:12 -0400 Subject: [PATCH 177/251] update config glossary and default values --- .../ConfigurationsGlossary.md | 39 +++++++++++-------- packages/client/.env.template | 5 ++- packages/client/src/Config.ts | 6 ++- .../default-app/packages/client/src/Config.ts | 1 - packages/integration-tests/package.json | 2 +- 5 files changed, 31 insertions(+), 22 deletions(-) diff --git a/microsite/docs/ConfigurationOptions/ConfigurationsGlossary.md b/microsite/docs/ConfigurationOptions/ConfigurationsGlossary.md index 2d5f6a06e..1db4e7bc3 100644 --- a/microsite/docs/ConfigurationOptions/ConfigurationsGlossary.md +++ b/microsite/docs/ConfigurationOptions/ConfigurationsGlossary.md @@ -7,7 +7,7 @@ sidebar_position: 4 ## Api/cli Packages -### Variables for both estimation approaches with AWS: +### Variables for both estimation approaches with AWS | Variable | Example Value | Type | Notes | | ---------------------------- | ----------------------------- | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | @@ -15,7 +15,7 @@ sidebar_position: 4
-### Variables needed for the Billing Data (Holistic) approach with AWS: +### Variables needed for the Billing Data (Holistic) approach with AWS | Variable | Example Value | Type | Notes | | -------------------------------- | --------------------------------------- | ------- | ------------------------------------------------------------------------------------------------------ | @@ -30,7 +30,7 @@ sidebar_position: 4
-### Variables needed for the Cloud Usage API (Higher Accuracy) approach with AWS: +### Variables needed for the Cloud Usage API (Higher Accuracy) approach with AWS | Variable | Example Value | Type | Notes | | | ------------ | ---------------------------------------------------- | ----- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | --- | @@ -38,7 +38,7 @@ sidebar_position: 4
-### Optionally set these AWS variables: +### Optionally set these AWS variables | Variable | Example Value | Type | Notes | | ---------------------------- | ------------------------ | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | @@ -50,7 +50,7 @@ sidebar_position: 4
-### Variables needed for the Billing Data (Holistic) approach with GCP: +### Variables needed for the Billing Data (Holistic) approach with GCP | Variable | Example Value | Type | Notes | | ------------------------------ | ------------------------------ | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | @@ -63,7 +63,7 @@ sidebar_position: 4
-### Variables needed for the Cloud Usage API (Higher Accuracy) approach with GCP: +### Variables needed for the Cloud Usage API (Higher Accuracy) approach with GCP | Variable | Example Value | Type | Notes | | ------------ | -------------------------------------------------------- | ----- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | @@ -71,7 +71,7 @@ sidebar_position: 4
-### Optionally set these GCP variables: +### Optionally set these GCP variables | Variable | Example Value | Type | Notes | | ---------------------------------------- | ------------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | @@ -81,7 +81,7 @@ sidebar_position: 4
-### Variables needed for the Billing Data (Holistic) approach with Azure: +### Variables needed for the Billing Data (Holistic) approach with Azure | Variable | Example Value | Type | Notes | | | ----------------------- | ------------------------ | ------- | ------------------------------------------------------------------------------------------------------- | --- | @@ -93,7 +93,7 @@ sidebar_position: 4
-### Optionally set this to "GCP" if your Azure credentials are stored in Google Secrets Manager: +### Optionally set this to "GCP" if your Azure credentials are stored in Google Secrets Manager | Variable | Example Value | Type | Notes | | --------------- | ------------- | ------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | @@ -107,7 +107,7 @@ sidebar_position: 4 | ------------------------ | -------------------------------------- | ------------ | ---------------------------------------------------------------------------------------------- | --- | | AZURE_RESOURCE_TAG_NAMES | ["resourceGroup","project","customer"] | array:string | Azure resource tag names to include if present, include resourceGroup as a tag name if needed. | -### Optionally set this to customize usage data fetch behavior. See [Azure Performance Considerations](./PerformanceConsiderations.md#azure-performance-considerations) for more information. +### Optionally set this to customize usage data fetch behavior. See [Azure Performance Considerations](./PerformanceConsiderations.md#azure-performance-considerations) for more information | Variable | Example Value | Type | Notes | | | ---------------------------- | ------------------------------------ | ------------ | ------------------------------------------------------------------------------------------------------------ | --- | @@ -139,20 +139,25 @@ sidebar_position: 4 ### Optionally set these variables to configure CORS -| Varibale | Example Value | Type | Notes | +| Variable | Example Value | Type | Notes | | ----------------- | ---------------------------------------- | ------- | ---------------------------------------------------------------------------------------------------------- | | ENABLE_CORS | true | boolean | Enables default CORS headers on all API requests. By default all origins, methods and headers are allowed. | -| CORS_ALLOW_ORIGIN | https://example.com,https://example2.com | string | A list of one or more origins to allow for CORS requests, comma separated. | +| CORS_ALLOW_ORIGIN | | string | A list of one or more origins to allow for CORS requests, comma separated. |
## Client Package - all variables are optional -| Variable | Example Value | Type | Notes | +| Variable | Example Value | Type Notes | | -------------------------------- | ----------------------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | | REACT_APP_PREVIOUS_YEAR_OF_USAGE | true | boolean | Use this to ensure the application requests usage data from the entire previous calendar year to today. Unset to make this false. Defaults to true. | | REACT_APP_GROUP_BY | month | string | Value to set how the cloud provider queries should return data (e.g. day/week/month/quarter/year). Defaults to day. | -| REACT_APP_DATE_RANGE_VALUE | 1 | number | The quantity of REACT_APP_DATE_RANGE_TYPE to be used. | -| REACT_APP_DATE_RANGE_TYPE | year | string | The type of time period to be used. Values can be day(s), week(s), month(s), quarter(s), year(s) | -| REACT_APP_MINIMAL_DATE_AGE | 1 | number | The amount of days to subtract from current date as end date. | -| REACT_APP_BASE_URL | https://example.com/api | string | The base URL used to make API requests. | +| REACT_APP_START_DATE | 01-01-2022 | string | The date range to query data based on custom start/end timestamps (takes precedence over legacy config). Defaults to current data | +| REACT_APP_END_DATE | 06-01-2022 | string | Set the date range to query data based on custom start/end timestamps. Defaults to 30 days prior | +| REACT_APP_DATE_RANGE_VALUE | 1 | number | The quantity of REACT_APP_DATE_RANGE_TYPE to be used. (Legacy date range config) | +| REACT_APP_DATE_RANGE_TYPE | year | string | The type of time period to be used. Values can be day(s), week(s), month(s), quarter(s), year(s) (Legacy date range config) | +| REACT_APP_MINIMAL_DATE_AGE | 1 | number | The amount of days to subtract from current date as end date. (Legacy date range config) | +| REACT_APP_BASE_URL | | string | The base URL used to make API requests. | +| REACT_APP_DISABLE_CACHE | true | boolean | Set to true to disable caching. Defaults to false. | +| REACT_APP_DISABLE_FORECAST_VALIDATION | true | boolean | Set to true to disable recommendations forecast validation. Defaults to false. (For accurate forecasts, it is recommended to keep this enabled) | +| REACT_APP_PAGE_LIMIT | 50000 | number | The pagination limit for fetched estimates per request. Defaults to 50000. (MongoDB Only, recommended to not exceed 50000) | diff --git a/packages/client/.env.template b/packages/client/.env.template index 59b356fc7..1df45a617 100644 --- a/packages/client/.env.template +++ b/packages/client/.env.template @@ -15,6 +15,9 @@ REACT_APP_END_DATE=06-01-2022 REACT_APP_DATE_RANGE_VALUE=2 REACT_APP_DATE_RANGE_TYPE=months +# Optionally configure the API base URL to use a custom API endpoint +REACT-APP_BASE_URL=https://example.com/api + # Optionally configure the footprint estimation results to group by day(s), month(s), quarter(s) or year(s). # More info: https://www.cloudcarbonfootprint.org/docs/performance-configurations/ REACT_APP_GROUP_BY=day @@ -28,4 +31,4 @@ REACT_APP_DISABLE_FORECAST_VALIDATION=true # MongoDB config # When using MongoDB as cache, large results will be paginated and split into multiple requests. Use this to set a limit for results per page/request. -PAGE_LIMIT=30 +REACT_APP_PAGE_LIMIT=50000 diff --git a/packages/client/src/Config.ts b/packages/client/src/Config.ts index a11effd99..4f9c90305 100644 --- a/packages/client/src/Config.ts +++ b/packages/client/src/Config.ts @@ -31,6 +31,8 @@ let pageLimit = process.env.REACT_APP_PAGE_LIMIT || '50000' let baseUrl = process.env.REACT_APP_BASE_URL || '/api' let endDate = process.env.REACT_APP_END_DATE let minDateAge = process.env.REACT_APP_MINIMAL_DATE_AGE || '0' +let disableForecastValidation = + process.env.REACT_APP_DISABLE_FORECAST_VALIDATION === 'true' // For local development / integration testing if (process.env.REACT_APP_TEST_MODE === 'true') { @@ -40,6 +42,7 @@ if (process.env.REACT_APP_TEST_MODE === 'true') { baseUrl = 'http://127.0.0.1:3000/api' endDate = null minDateAge = '0' + disableForecastValidation = false } const appConfig: ClientConfig = { @@ -61,8 +64,7 @@ const appConfig: ClientConfig = { START_DATE: process.env.REACT_APP_START_DATE, END_DATE: endDate, DISABLE_CACHE: process.env.REACT_APP_DISABLE_CACHE === 'true', - DISABLE_FORECAST_VALIDATION: - process.env.REACT_APP_DISABLE_FORECAST_VALIDATION === 'true', + DISABLE_FORECAST_VALIDATION: disableForecastValidation, TEST_MODE: process.env.REACT_APP_TEST_MODE === 'true', } diff --git a/packages/create-app/templates/default-app/packages/client/src/Config.ts b/packages/create-app/templates/default-app/packages/client/src/Config.ts index a11effd99..9bfdd55e9 100644 --- a/packages/create-app/templates/default-app/packages/client/src/Config.ts +++ b/packages/create-app/templates/default-app/packages/client/src/Config.ts @@ -47,7 +47,6 @@ const appConfig: ClientConfig = { { key: 'aws', name: 'AWS' }, { key: 'gcp', name: 'GCP' }, { key: 'azure', name: 'Azure' }, - { key: 'alicloud', name: 'AliCloud' }, ], PREVIOUS_YEAR_OF_USAGE: previousYearOfUsage, DATE_RANGE: { diff --git a/packages/integration-tests/package.json b/packages/integration-tests/package.json index c83074821..8e132e045 100644 --- a/packages/integration-tests/package.json +++ b/packages/integration-tests/package.json @@ -5,7 +5,7 @@ "description": "Test repository to run integration tests", "scripts": { "start": "concurrently \"yarn start-client\" \"yarn start-api\"", - "start-client": "BROWSER=none REACT_APP_TEST_MODE=true REACT_APP_DISABLE_FORECAST_VALIDATION=false yarn workspace @cloud-carbon-footprint/client start", + "start-client": "BROWSER=none REACT_APP_TEST_MODE=true yarn workspace @cloud-carbon-footprint/client start", "start-api": "TEST_MODE=true CACHE_MODE=LOCAL ENABLE_CORS=false yarn workspace @cloud-carbon-footprint/api start", "headless": "testcafe 'chromium:headless' 'tests/*test.js'", "headed": "testcafe 'chrome'", From 1b5b11260cf48dbe0281a04c5348e382a70b89f5 Mon Sep 17 00:00:00 2001 From: Arik Smith Date: Mon, 17 Jul 2023 16:58:12 -0400 Subject: [PATCH 178/251] update create-app templates --- .../templates/default-app/packages/client/src/Config.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/create-app/templates/default-app/packages/client/src/Config.ts b/packages/create-app/templates/default-app/packages/client/src/Config.ts index 9bfdd55e9..4f9c90305 100644 --- a/packages/create-app/templates/default-app/packages/client/src/Config.ts +++ b/packages/create-app/templates/default-app/packages/client/src/Config.ts @@ -31,6 +31,8 @@ let pageLimit = process.env.REACT_APP_PAGE_LIMIT || '50000' let baseUrl = process.env.REACT_APP_BASE_URL || '/api' let endDate = process.env.REACT_APP_END_DATE let minDateAge = process.env.REACT_APP_MINIMAL_DATE_AGE || '0' +let disableForecastValidation = + process.env.REACT_APP_DISABLE_FORECAST_VALIDATION === 'true' // For local development / integration testing if (process.env.REACT_APP_TEST_MODE === 'true') { @@ -40,6 +42,7 @@ if (process.env.REACT_APP_TEST_MODE === 'true') { baseUrl = 'http://127.0.0.1:3000/api' endDate = null minDateAge = '0' + disableForecastValidation = false } const appConfig: ClientConfig = { @@ -47,6 +50,7 @@ const appConfig: ClientConfig = { { key: 'aws', name: 'AWS' }, { key: 'gcp', name: 'GCP' }, { key: 'azure', name: 'Azure' }, + { key: 'alicloud', name: 'AliCloud' }, ], PREVIOUS_YEAR_OF_USAGE: previousYearOfUsage, DATE_RANGE: { @@ -60,8 +64,7 @@ const appConfig: ClientConfig = { START_DATE: process.env.REACT_APP_START_DATE, END_DATE: endDate, DISABLE_CACHE: process.env.REACT_APP_DISABLE_CACHE === 'true', - DISABLE_FORECAST_VALIDATION: - process.env.REACT_APP_DISABLE_FORECAST_VALIDATION === 'true', + DISABLE_FORECAST_VALIDATION: disableForecastValidation, TEST_MODE: process.env.REACT_APP_TEST_MODE === 'true', } From e1a603a268dfe66204b84e93b1014e3ac6403f33 Mon Sep 17 00:00:00 2001 From: Arik Smith Date: Mon, 17 Jul 2023 16:58:50 -0400 Subject: [PATCH 179/251] changeset: add support for disabling recommendations forecast date validation --- .changeset/unlucky-ties-chew.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changeset/unlucky-ties-chew.md diff --git a/.changeset/unlucky-ties-chew.md b/.changeset/unlucky-ties-chew.md new file mode 100644 index 000000000..1f0ccebe5 --- /dev/null +++ b/.changeset/unlucky-ties-chew.md @@ -0,0 +1,6 @@ +--- +'@cloud-carbon-footprint/client': minor +'@cloud-carbon-footprint/create-app': minor +--- + +Adds config for disabling forecast date validation From d36b238b4a52f739c954056322ea0784d81a6c96 Mon Sep 17 00:00:00 2001 From: mountainaireman Date: Mon, 10 Jul 2023 11:21:17 -0500 Subject: [PATCH 180/251] Update AzureRegions.ts --- packages/azure/src/lib/AzureRegions.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/azure/src/lib/AzureRegions.ts b/packages/azure/src/lib/AzureRegions.ts index a91f105f4..0f1e6c6ef 100644 --- a/packages/azure/src/lib/AzureRegions.ts +++ b/packages/azure/src/lib/AzureRegions.ts @@ -115,15 +115,15 @@ export const AZURE_REGIONS = { US_CANADA: { name: 'canada', options: ['canada'] }, US_CANADA_CENTRAL: { name: 'canadacentral', options: ['canadacentral'] }, US_CANADA_EAST: { name: 'canadaeast', options: ['canadaeast'] }, - US_CENTRAL: { name: 'CentralUS', options: ['CentralUS', 'US Central'] }, + US_CENTRAL: { name: 'CentralUS', options: ['CentralUS', 'US Central', 'centralus', 'CENTRALUS'] }, US_CENTRAL_EUAP: { name: 'centraluseuap', options: ['centraluseuap'] }, US_CENTRAL_STAGE: { name: 'centralusstage', options: ['centralusstage'] }, US_EAST: { name: 'EastUS', - options: ['EastUS', 'EASTUS', 'USEast', 'useast', 'US East'], + options: ['EastUS', 'EASTUS', 'USEast', 'useast', 'US East', 'eastus'], }, US_EAST_STAGE: { name: 'eastusstage', options: ['eastusstage'] }, - US_EAST_2: { name: 'EastUS2', options: ['EastUS2', 'useast2', 'US East 2'] }, + US_EAST_2: { name: 'EastUS2', options: ['EastUS2', 'useast2', 'US East 2', 'eastus2', 'EASTUS2'] }, US_EAST_2_EUAP: { name: 'eastus2euap', options: ['eastus2euap'] }, US_EAST_2_STAGE: { name: 'eastus2stage', options: ['eastus2stage'] }, US_EAST_3: { name: 'EastUS3', options: ['EastUS3'] }, @@ -135,7 +135,7 @@ export const AZURE_REGIONS = { name: 'northcentralusstage', options: ['northcentralusstage'], }, - US_SOUTH_CENTRAL: { name: 'SouthCentralUS', options: ['SouthCentralUS'] }, + US_SOUTH_CENTRAL: { name: 'SouthCentralUS', options: ['SouthCentralUS', 'southcentralus'] }, US_SOUTH_CENTRAL_STAGE: { name: 'southcentralusstage', options: ['southcentralusstage'], @@ -144,7 +144,7 @@ export const AZURE_REGIONS = { US_US_EAP: { name: 'unitedstateseuap', options: ['unitedstateseuap'] }, US_WEST_CENTRAL: { name: 'WestCentralUS', - options: ['WestCentralUS', 'westcentralus', 'US West Central'], + options: ['WestCentralUS', 'westcentralus', 'US West Central', 'WESTCENTRALUS'], }, US_NORTH: { name: 'USNorth', options: ['USNorth'] }, US_WEST: { name: 'WestUS', options: ['WestUS', 'westus', 'US West'] }, @@ -161,7 +161,7 @@ export const AZURE_REGIONS = { ALL: { name: 'All Regions', - options: ['All Regions', 'GLOBAL', 'Intercontinental'], + options: ['All Regions', 'GLOBAL', 'Intercontinental', 'global'], }, UNASSIGNED: { name: 'Unassigned', options: ['Unassigned'] }, UNKNOWN: { name: 'Unknown', options: ['Unknown'] }, From 02e729ba7b66bb313a630a3700f58682ef4d46d9 Mon Sep 17 00:00:00 2001 From: Christopher Krull Date: Mon, 10 Jul 2023 14:54:32 -0500 Subject: [PATCH 181/251] Lint --- packages/azure/src/lib/AzureRegions.ts | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/packages/azure/src/lib/AzureRegions.ts b/packages/azure/src/lib/AzureRegions.ts index 0f1e6c6ef..0a4743e49 100644 --- a/packages/azure/src/lib/AzureRegions.ts +++ b/packages/azure/src/lib/AzureRegions.ts @@ -115,7 +115,10 @@ export const AZURE_REGIONS = { US_CANADA: { name: 'canada', options: ['canada'] }, US_CANADA_CENTRAL: { name: 'canadacentral', options: ['canadacentral'] }, US_CANADA_EAST: { name: 'canadaeast', options: ['canadaeast'] }, - US_CENTRAL: { name: 'CentralUS', options: ['CentralUS', 'US Central', 'centralus', 'CENTRALUS'] }, + US_CENTRAL: { + name: 'CentralUS', + options: ['CentralUS', 'US Central', 'centralus', 'CENTRALUS'], + }, US_CENTRAL_EUAP: { name: 'centraluseuap', options: ['centraluseuap'] }, US_CENTRAL_STAGE: { name: 'centralusstage', options: ['centralusstage'] }, US_EAST: { @@ -123,7 +126,10 @@ export const AZURE_REGIONS = { options: ['EastUS', 'EASTUS', 'USEast', 'useast', 'US East', 'eastus'], }, US_EAST_STAGE: { name: 'eastusstage', options: ['eastusstage'] }, - US_EAST_2: { name: 'EastUS2', options: ['EastUS2', 'useast2', 'US East 2', 'eastus2', 'EASTUS2'] }, + US_EAST_2: { + name: 'EastUS2', + options: ['EastUS2', 'useast2', 'US East 2', 'eastus2', 'EASTUS2'], + }, US_EAST_2_EUAP: { name: 'eastus2euap', options: ['eastus2euap'] }, US_EAST_2_STAGE: { name: 'eastus2stage', options: ['eastus2stage'] }, US_EAST_3: { name: 'EastUS3', options: ['EastUS3'] }, @@ -135,7 +141,10 @@ export const AZURE_REGIONS = { name: 'northcentralusstage', options: ['northcentralusstage'], }, - US_SOUTH_CENTRAL: { name: 'SouthCentralUS', options: ['SouthCentralUS', 'southcentralus'] }, + US_SOUTH_CENTRAL: { + name: 'SouthCentralUS', + options: ['SouthCentralUS', 'southcentralus'], + }, US_SOUTH_CENTRAL_STAGE: { name: 'southcentralusstage', options: ['southcentralusstage'], @@ -144,7 +153,12 @@ export const AZURE_REGIONS = { US_US_EAP: { name: 'unitedstateseuap', options: ['unitedstateseuap'] }, US_WEST_CENTRAL: { name: 'WestCentralUS', - options: ['WestCentralUS', 'westcentralus', 'US West Central', 'WESTCENTRALUS'], + options: [ + 'WestCentralUS', + 'westcentralus', + 'US West Central', + 'WESTCENTRALUS', + ], }, US_NORTH: { name: 'USNorth', options: ['USNorth'] }, US_WEST: { name: 'WestUS', options: ['WestUS', 'westus', 'US West'] }, From c29f88f827756e225c8c51e8b8273fd813e205d6 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 17 Jul 2023 21:08:49 +0000 Subject: [PATCH 182/251] Version Packages --- .changeset/fuzzy-insects-develop.md | 5 ----- .changeset/ten-pets-buy.md | 5 ----- .changeset/unlucky-ties-chew.md | 6 ------ packages/aws/CHANGELOG.md | 6 ++++++ packages/aws/package.json | 2 +- packages/client/CHANGELOG.md | 6 ++++++ packages/client/package.json | 2 +- packages/create-app/CHANGELOG.md | 6 ++++++ packages/create-app/package.json | 2 +- packages/integration-tests/CHANGELOG.md | 6 ++++++ packages/integration-tests/package.json | 2 +- 11 files changed, 28 insertions(+), 20 deletions(-) delete mode 100644 .changeset/fuzzy-insects-develop.md delete mode 100644 .changeset/ten-pets-buy.md delete mode 100644 .changeset/unlucky-ties-chew.md diff --git a/.changeset/fuzzy-insects-develop.md b/.changeset/fuzzy-insects-develop.md deleted file mode 100644 index 6b2edbff0..000000000 --- a/.changeset/fuzzy-insects-develop.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@cloud-carbon-footprint/aws': patch ---- - -Fixes Athena query column error for accounts without EC2 hours/usage diff --git a/.changeset/ten-pets-buy.md b/.changeset/ten-pets-buy.md deleted file mode 100644 index b473e229c..000000000 --- a/.changeset/ten-pets-buy.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@cloud-carbon-footprint/integration-tests': patch ---- - -Fixes failing recommendations test diff --git a/.changeset/unlucky-ties-chew.md b/.changeset/unlucky-ties-chew.md deleted file mode 100644 index 1f0ccebe5..000000000 --- a/.changeset/unlucky-ties-chew.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@cloud-carbon-footprint/client': minor -'@cloud-carbon-footprint/create-app': minor ---- - -Adds config for disabling forecast date validation diff --git a/packages/aws/CHANGELOG.md b/packages/aws/CHANGELOG.md index e33cc06f8..3a27667bc 100644 --- a/packages/aws/CHANGELOG.md +++ b/packages/aws/CHANGELOG.md @@ -1,5 +1,11 @@ # @cloud-carbon-footprint/aws +## 0.14.5 + +### Patch Changes + +- 7fe74989: Fixes Athena query column error for accounts without EC2 hours/usage + ## 0.14.4 ### Patch Changes diff --git a/packages/aws/package.json b/packages/aws/package.json index fea9c2816..065f36064 100644 --- a/packages/aws/package.json +++ b/packages/aws/package.json @@ -1,6 +1,6 @@ { "name": "@cloud-carbon-footprint/aws", - "version": "0.14.4", + "version": "0.14.5", "license": "Apache-2.0", "description": "The core logic to get cloud usage data and estimate energy and carbon emissions from Amazon Web Services.", "main": "src/index.ts", diff --git a/packages/client/CHANGELOG.md b/packages/client/CHANGELOG.md index ae1a875b8..77329b380 100644 --- a/packages/client/CHANGELOG.md +++ b/packages/client/CHANGELOG.md @@ -1,5 +1,11 @@ # @cloud-carbon-footprint/client +## 4.2.0 + +### Minor Changes + +- e1a603a2: Adds config for disabling forecast date validation + ## 4.1.3 ### Patch Changes diff --git a/packages/client/package.json b/packages/client/package.json index 9c2c02489..533286d14 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -1,6 +1,6 @@ { "name": "@cloud-carbon-footprint/client", - "version": "4.1.3", + "version": "4.2.0", "license": "Apache-2.0", "description": "The front-end dashboard for Cloud Carbon Footprint.", "main": "src/index.tsx", diff --git a/packages/create-app/CHANGELOG.md b/packages/create-app/CHANGELOG.md index 3867bf774..e525f4e25 100644 --- a/packages/create-app/CHANGELOG.md +++ b/packages/create-app/CHANGELOG.md @@ -1,5 +1,11 @@ # @cloud-carbon-footprint/create-app +## 2.5.0 + +### Minor Changes + +- e1a603a2: Adds config for disabling forecast date validation + ## 2.4.0 ### Minor Changes diff --git a/packages/create-app/package.json b/packages/create-app/package.json index 78e70dbe0..7bee59de9 100644 --- a/packages/create-app/package.json +++ b/packages/create-app/package.json @@ -1,6 +1,6 @@ { "name": "@cloud-carbon-footprint/create-app", - "version": "2.4.0", + "version": "2.5.0", "license": "Apache-2.0", "description": "Create app package for Cloud Carbon Footprint", "main": "dist/index.js", diff --git a/packages/integration-tests/CHANGELOG.md b/packages/integration-tests/CHANGELOG.md index 07119469f..b9975ca60 100644 --- a/packages/integration-tests/CHANGELOG.md +++ b/packages/integration-tests/CHANGELOG.md @@ -1,5 +1,11 @@ # @cloud-carbon-footprint/integration-tests +## 1.1.8 + +### Patch Changes + +- 6d4ce5f2: Fixes failing recommendations test + ## 1.1.7 ### Patch Changes diff --git a/packages/integration-tests/package.json b/packages/integration-tests/package.json index 8e132e045..fbd3a07df 100644 --- a/packages/integration-tests/package.json +++ b/packages/integration-tests/package.json @@ -1,6 +1,6 @@ { "name": "@cloud-carbon-footprint/integration-tests", - "version": "1.1.7", + "version": "1.1.8", "private": true, "description": "Test repository to run integration tests", "scripts": { From c515ba60a772d9e6410406677f85dfc67432b0a4 Mon Sep 17 00:00:00 2001 From: Arik Smith Date: Mon, 17 Jul 2023 17:19:01 -0400 Subject: [PATCH 183/251] changeset: Adds aditional aliases for Azure regions --- .changeset/lucky-olives-suffer.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/lucky-olives-suffer.md diff --git a/.changeset/lucky-olives-suffer.md b/.changeset/lucky-olives-suffer.md new file mode 100644 index 000000000..b8ccb4fdd --- /dev/null +++ b/.changeset/lucky-olives-suffer.md @@ -0,0 +1,5 @@ +--- +'@cloud-carbon-footprint/azure': patch +--- + +Adds aditional aliases for Azure regions From 34e774ffb84e21f31257e49d9b00c3561aa5722b Mon Sep 17 00:00:00 2001 From: Arik Smith Date: Mon, 17 Jul 2023 17:39:06 -0400 Subject: [PATCH 184/251] disable AliCloud map from UI --- .../CarbonIntensityMap/CarbonIntensityMap.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/client/src/pages/EmissionsMetricsPage/CarbonIntensityMap/CarbonIntensityMap.tsx b/packages/client/src/pages/EmissionsMetricsPage/CarbonIntensityMap/CarbonIntensityMap.tsx index aacebbe54..ef788bf97 100644 --- a/packages/client/src/pages/EmissionsMetricsPage/CarbonIntensityMap/CarbonIntensityMap.tsx +++ b/packages/client/src/pages/EmissionsMetricsPage/CarbonIntensityMap/CarbonIntensityMap.tsx @@ -11,7 +11,8 @@ import GCPMap from './GCPMap.png' import AzureMap from './AzureMap.png' import useStyles from './carbonIntensityStyles' -type CloudProvider = 'AWS' | 'GCP' | 'Azure' | 'AliCloud' +type CloudProvider = 'AWS' | 'GCP' | 'Azure' +//| 'AliCloud' - TODO: Add back when map is ready type IntensityMaps = { [provider in CloudProvider]: React.ReactNode @@ -25,7 +26,7 @@ const CarbonIntensityMap = (): ReactElement => { AWS: AWSMap, GCP: GCPMap, Azure: AzureMap, - AliCloud: AWSMap, + // AliCloud: AliMap TODO: Add Carbon Intensity Map and enable } const handleChange = (event: React.ChangeEvent<{ value: string }>) => { From ec9e4c8f2bb86c83056eb0a50f1af0fcd9244f01 Mon Sep 17 00:00:00 2001 From: Arik Smith Date: Mon, 17 Jul 2023 17:41:01 -0400 Subject: [PATCH 185/251] temporarily disable updating mock data for the demo app --- .github/workflows/demo-app.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/demo-app.yml b/.github/workflows/demo-app.yml index ecbe1c490..e336427b6 100644 --- a/.github/workflows/demo-app.yml +++ b/.github/workflows/demo-app.yml @@ -71,7 +71,8 @@ jobs: mkdir packages/client/stub-server/api mkdir packages/client/stub-server/api/regions - node scripts/create-mock-data.js + # Updates the timestamps and values of the mock data files (usually for the forecast data) + # node scripts/create-mock-data.js # Copy static build files to demo Google Cloud Bucket gsutil cp -r packages/client/build/* gs://${GOOGLE_DEMO_STORAGE_BUCKET} From 6bf5745d57da5035dd5a25c3e4ce25a0edb4c6a9 Mon Sep 17 00:00:00 2001 From: Arik Smith Date: Mon, 17 Jul 2023 17:54:16 -0400 Subject: [PATCH 186/251] Revert "Version Packages" --- .changeset/fuzzy-insects-develop.md | 5 +++++ .changeset/ten-pets-buy.md | 5 +++++ .changeset/unlucky-ties-chew.md | 6 ++++++ packages/aws/CHANGELOG.md | 6 ------ packages/aws/package.json | 2 +- packages/client/CHANGELOG.md | 6 ------ packages/client/package.json | 2 +- packages/create-app/CHANGELOG.md | 6 ------ packages/create-app/package.json | 2 +- packages/integration-tests/CHANGELOG.md | 6 ------ packages/integration-tests/package.json | 2 +- 11 files changed, 20 insertions(+), 28 deletions(-) create mode 100644 .changeset/fuzzy-insects-develop.md create mode 100644 .changeset/ten-pets-buy.md create mode 100644 .changeset/unlucky-ties-chew.md diff --git a/.changeset/fuzzy-insects-develop.md b/.changeset/fuzzy-insects-develop.md new file mode 100644 index 000000000..6b2edbff0 --- /dev/null +++ b/.changeset/fuzzy-insects-develop.md @@ -0,0 +1,5 @@ +--- +'@cloud-carbon-footprint/aws': patch +--- + +Fixes Athena query column error for accounts without EC2 hours/usage diff --git a/.changeset/ten-pets-buy.md b/.changeset/ten-pets-buy.md new file mode 100644 index 000000000..b473e229c --- /dev/null +++ b/.changeset/ten-pets-buy.md @@ -0,0 +1,5 @@ +--- +'@cloud-carbon-footprint/integration-tests': patch +--- + +Fixes failing recommendations test diff --git a/.changeset/unlucky-ties-chew.md b/.changeset/unlucky-ties-chew.md new file mode 100644 index 000000000..1f0ccebe5 --- /dev/null +++ b/.changeset/unlucky-ties-chew.md @@ -0,0 +1,6 @@ +--- +'@cloud-carbon-footprint/client': minor +'@cloud-carbon-footprint/create-app': minor +--- + +Adds config for disabling forecast date validation diff --git a/packages/aws/CHANGELOG.md b/packages/aws/CHANGELOG.md index 3a27667bc..e33cc06f8 100644 --- a/packages/aws/CHANGELOG.md +++ b/packages/aws/CHANGELOG.md @@ -1,11 +1,5 @@ # @cloud-carbon-footprint/aws -## 0.14.5 - -### Patch Changes - -- 7fe74989: Fixes Athena query column error for accounts without EC2 hours/usage - ## 0.14.4 ### Patch Changes diff --git a/packages/aws/package.json b/packages/aws/package.json index 065f36064..fea9c2816 100644 --- a/packages/aws/package.json +++ b/packages/aws/package.json @@ -1,6 +1,6 @@ { "name": "@cloud-carbon-footprint/aws", - "version": "0.14.5", + "version": "0.14.4", "license": "Apache-2.0", "description": "The core logic to get cloud usage data and estimate energy and carbon emissions from Amazon Web Services.", "main": "src/index.ts", diff --git a/packages/client/CHANGELOG.md b/packages/client/CHANGELOG.md index 77329b380..ae1a875b8 100644 --- a/packages/client/CHANGELOG.md +++ b/packages/client/CHANGELOG.md @@ -1,11 +1,5 @@ # @cloud-carbon-footprint/client -## 4.2.0 - -### Minor Changes - -- e1a603a2: Adds config for disabling forecast date validation - ## 4.1.3 ### Patch Changes diff --git a/packages/client/package.json b/packages/client/package.json index 533286d14..9c2c02489 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -1,6 +1,6 @@ { "name": "@cloud-carbon-footprint/client", - "version": "4.2.0", + "version": "4.1.3", "license": "Apache-2.0", "description": "The front-end dashboard for Cloud Carbon Footprint.", "main": "src/index.tsx", diff --git a/packages/create-app/CHANGELOG.md b/packages/create-app/CHANGELOG.md index e525f4e25..3867bf774 100644 --- a/packages/create-app/CHANGELOG.md +++ b/packages/create-app/CHANGELOG.md @@ -1,11 +1,5 @@ # @cloud-carbon-footprint/create-app -## 2.5.0 - -### Minor Changes - -- e1a603a2: Adds config for disabling forecast date validation - ## 2.4.0 ### Minor Changes diff --git a/packages/create-app/package.json b/packages/create-app/package.json index 7bee59de9..78e70dbe0 100644 --- a/packages/create-app/package.json +++ b/packages/create-app/package.json @@ -1,6 +1,6 @@ { "name": "@cloud-carbon-footprint/create-app", - "version": "2.5.0", + "version": "2.4.0", "license": "Apache-2.0", "description": "Create app package for Cloud Carbon Footprint", "main": "dist/index.js", diff --git a/packages/integration-tests/CHANGELOG.md b/packages/integration-tests/CHANGELOG.md index b9975ca60..07119469f 100644 --- a/packages/integration-tests/CHANGELOG.md +++ b/packages/integration-tests/CHANGELOG.md @@ -1,11 +1,5 @@ # @cloud-carbon-footprint/integration-tests -## 1.1.8 - -### Patch Changes - -- 6d4ce5f2: Fixes failing recommendations test - ## 1.1.7 ### Patch Changes diff --git a/packages/integration-tests/package.json b/packages/integration-tests/package.json index fbd3a07df..8e132e045 100644 --- a/packages/integration-tests/package.json +++ b/packages/integration-tests/package.json @@ -1,6 +1,6 @@ { "name": "@cloud-carbon-footprint/integration-tests", - "version": "1.1.8", + "version": "1.1.7", "private": true, "description": "Test repository to run integration tests", "scripts": { From 879966d596bb5fb7e7e7eb1f05d635ab4f6b01c5 Mon Sep 17 00:00:00 2001 From: Arik Smith Date: Mon, 17 Jul 2023 17:58:25 -0400 Subject: [PATCH 187/251] set ali version to initial number --- packages/ali/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ali/package.json b/packages/ali/package.json index af120e068..9f211ff78 100644 --- a/packages/ali/package.json +++ b/packages/ali/package.json @@ -1,6 +1,6 @@ { "name": "@cloud-carbon-footprint/ali", - "version": "0.14.2", + "version": "0.1.0", "license": "Apache-2.0", "description": "The core logic to get cloud usage data and estimate energy and carbon emissions from Amazon Web Services.", "main": "src/index.ts", From 61b2d211cbf55c61f58af47eb7717f720ed62946 Mon Sep 17 00:00:00 2001 From: Arik Smith Date: Mon, 17 Jul 2023 18:07:42 -0400 Subject: [PATCH 188/251] update demo app workflow to allow for manual trigger --- .github/workflows/demo-app.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/demo-app.yml b/.github/workflows/demo-app.yml index e336427b6..b53007208 100644 --- a/.github/workflows/demo-app.yml +++ b/.github/workflows/demo-app.yml @@ -13,7 +13,7 @@ concurrency: cancel-in-progress: true jobs: deploy-demo-app: - if: github.event.pull_request.head.ref == 'changeset-release/trunk' + if: github.event_name == 'workflow_dispatch' || github.event.pull_request.head.ref == 'changeset-release/trunk' runs-on: ubuntu-latest container: image: node:16.19-alpine3.17 From 4eca2caed45c037f22eff5cdf44bb00eaaf1de79 Mon Sep 17 00:00:00 2001 From: Arik Smith Date: Mon, 17 Jul 2023 18:46:35 -0400 Subject: [PATCH 189/251] re-add mock data script to demo app workflow and remove ali cloud inclusion --- .github/workflows/demo-app.yml | 2 +- scripts/update-mock-data.js | 13 +++++++++---- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/.github/workflows/demo-app.yml b/.github/workflows/demo-app.yml index b53007208..335bfb3ff 100644 --- a/.github/workflows/demo-app.yml +++ b/.github/workflows/demo-app.yml @@ -72,7 +72,7 @@ jobs: mkdir packages/client/stub-server/api/regions # Updates the timestamps and values of the mock data files (usually for the forecast data) - # node scripts/create-mock-data.js + node scripts/create-mock-data.js # Copy static build files to demo Google Cloud Bucket gsutil cp -r packages/client/build/* gs://${GOOGLE_DEMO_STORAGE_BUCKET} diff --git a/scripts/update-mock-data.js b/scripts/update-mock-data.js index e9dbcabe1..7f786e436 100644 --- a/scripts/update-mock-data.js +++ b/scripts/update-mock-data.js @@ -20,10 +20,15 @@ async function update() { updatedMonth = getPreviousMonth(updatedMonth) footprint.serviceEstimates.forEach((serviceEstimate) => { - const regionObj = mockData.emissions.find(o => o.region === serviceEstimate.region) - const { mtPerKwHour } = regionObj - const updatedC02e = serviceEstimate.kilowattHours * mtPerKwHour - serviceEstimate.co2e = updatedC02e + // TODO: Remove this until we have AliCloud mock Emissions data + if (serviceEstimate.cloudProvider !== 'AliCloud') { + const regionObj = mockData.emissions.find( + (o) => o.region === serviceEstimate.region, + ) + const { mtPerKwHour } = regionObj + const updatedC02e = serviceEstimate.kilowattHours * mtPerKwHour + serviceEstimate.co2e = updatedC02e + } }) }) From 6669f14d9199e2c7f6c5d53b84c5735d1f84415b Mon Sep 17 00:00:00 2001 From: Arik Smith Date: Mon, 17 Jul 2023 19:05:09 -0400 Subject: [PATCH 190/251] update checkout action version for demo app workflow --- .github/workflows/demo-app.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/demo-app.yml b/.github/workflows/demo-app.yml index 335bfb3ff..f332ab25d 100644 --- a/.github/workflows/demo-app.yml +++ b/.github/workflows/demo-app.yml @@ -24,7 +24,7 @@ jobs: steps: # Fetch and checkout the repo - name: Checkout repo - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: fetch-depth: 0 # Run Yarn Install in the Client Package From d9d6dd316fa4a008b25018aeb10dcd37616bc9cb Mon Sep 17 00:00:00 2001 From: Arik Smith Date: Tue, 18 Jul 2023 22:49:19 -0400 Subject: [PATCH 191/251] updates mock data with better cost correlation, disables forecast validation in mock data command --- packages/api/mock-estimates.json | 30 +- packages/client/package.json | 2 +- packages/client/stub-server/mockData.json | 1540 +-------------------- scripts/update-mock-data.js | 8 +- 4 files changed, 24 insertions(+), 1556 deletions(-) diff --git a/packages/api/mock-estimates.json b/packages/api/mock-estimates.json index 3af9294ea..f4981e6da 100644 --- a/packages/api/mock-estimates.json +++ b/packages/api/mock-estimates.json @@ -1,31 +1,31 @@ [ -{"timestamp":"2023-05-01T06:00:00.000Z","serviceEstimates":[{"cloudProvider":"AWS","accountName":"aws account 0","serviceName":"ebs","kilowattHours":0.9099318685999922,"co2e":0.00034492696349833045,"cost":2.0611247334106126,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 0","serviceName":"s3","kilowattHours":0.000986946262424086,"co2e":4.0524803092142913e-7,"cost":2.341122015683861,"region":"us-east-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"ec2","kilowattHours":50.176579830111365,"co2e":0.016165238194127487,"cost":2.4154850519158613,"region":"us-west-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"rds","kilowattHours":50.15554852182787,"co2e":0.01615846260063172,"cost":1.8825350541114,"region":"us-west-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 0","serviceName":"lambda","kilowattHours":72.26453941514701,"co2e":0.027393246691560364,"cost":2.0084835420769656,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"GCP","accountName":"gcp account 4","serviceName":"computeEngine","kilowattHours":68.54797801901817,"co2e":0.03290302944912873,"cost":1.8688785461442674,"region":"us-east1","usesAverageCPUConstant":false},{"cloudProvider":"AZURE","accountName":"azure account 4","serviceName":"virtualMachines","kilowattHours":50.02721869884774,"co2e":0.011256124207240741,"cost":2.2910467204304323,"region":"UK South","usesAverageCPUConstant":false}]} +{"timestamp":"2023-07-01T04:00:00.000Z","serviceEstimates":[{"cloudProvider":"AWS","accountName":"aws account 0","serviceName":"ebs","kilowattHours":0.9099318685999922,"co2e":0.00034492696349833045,"cost":0.14371956812430436,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 0","serviceName":"s3","kilowattHours":0.000986946262424086,"co2e":4.0524803092142913e-7,"cost":0.00016885334621726214,"region":"us-east-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"ec2","kilowattHours":50.176579830111365,"co2e":0.016165238194127487,"cost":6.7355159142197865,"region":"us-west-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"rds","kilowattHours":50.15554852182787,"co2e":0.01615846260063172,"cost":6.732692750263217,"region":"us-west-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 0","serviceName":"lambda","kilowattHours":72.26453941514701,"co2e":0.027393246691560364,"cost":11.413852788150153,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"GCP","accountName":"gcp account 4","serviceName":"computeEngine","kilowattHours":68.54797801901817,"co2e":0.03290302944912873,"cost":13.709595603803638,"region":"us-east1","usesAverageCPUConstant":false},{"cloudProvider":"AZURE","accountName":"azure account 4","serviceName":"virtualMachines","kilowattHours":50.02721869884774,"co2e":0.011256124207240741,"cost":4.690051753016976,"region":"UK South","usesAverageCPUConstant":false},{"cloudProvider":"AliCloud","accountName":"ali account 1","serviceName":"virtualMachines","kilowattHours":50.05357763407657,"co2e":0.012024032067235646,"cost":2.674481284318876,"region":"cn-hangzhou","usesAverageCPUConstant":false}]} , -{"timestamp":"2023-04-01T06:00:00.000Z","serviceEstimates":[{"cloudProvider":"AWS","accountName":"aws account 0","serviceName":"ebs","kilowattHours":0.9281382786569483,"co2e":0.00035182844915221075,"cost":2.225771432143803,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"s3","kilowattHours":0.00003270772530525257,"co2e":1.3430053672139147e-8,"cost":1.5028221506352941,"region":"us-east-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"ec2","kilowattHours":50.558103056454314,"co2e":0.016288152387388715,"cost":2.440745140900739,"region":"us-west-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"rds","kilowattHours":62.615457571422965,"co2e":0.02017263411941262,"cost":1.5955486508938537,"region":"us-west-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 0","serviceName":"lambda","kilowattHours":50.903421738830794,"co2e":0.01929590917511685,"cost":2.238206567880293,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"GCP","accountName":"gcp account 1","serviceName":"computeEngine","kilowattHours":65.28200479098749,"co2e":0.0050919963736970235,"cost":1.6177172869839958,"region":"us-west1","usesAverageCPUConstant":false},{"cloudProvider":"AZURE","accountName":"azure account 1","serviceName":"virtualMachines","kilowattHours":61.90994721222564,"co2e":0.013929738122750768,"cost":2.2081554041299234,"region":"UK South","usesAverageCPUConstant":false}]} +{"timestamp":"2023-06-01T04:00:00.000Z","serviceEstimates":[{"cloudProvider":"AWS","accountName":"aws account 0","serviceName":"ebs","kilowattHours":0.9281382786569483,"co2e":0.00035182844915221075,"cost":0.14659518714675449,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"s3","kilowattHours":0.00003270772530525257,"co2e":1.3430053672139147e-8,"cost":0.000005595855696724645,"region":"us-east-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"ec2","kilowattHours":50.558103056454314,"co2e":0.016288152387388715,"cost":6.786730161411965,"region":"us-west-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"rds","kilowattHours":62.615457571422965,"co2e":0.02017263411941262,"cost":8.405264216421926,"region":"us-west-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 0","serviceName":"lambda","kilowattHours":50.903421738830794,"co2e":0.01929590917511685,"cost":8.039962156298689,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"GCP","accountName":"gcp account 1","serviceName":"computeEngine","kilowattHours":65.28200479098749,"co2e":0.0050919963736970235,"cost":2.121665155707093,"region":"us-west1","usesAverageCPUConstant":false},{"cloudProvider":"AZURE","accountName":"azure account 1","serviceName":"virtualMachines","kilowattHours":61.90994721222564,"co2e":0.013929738122750768,"cost":5.804057551146154,"region":"UK South","usesAverageCPUConstant":false},{"cloudProvider":"AliCloud","accountName":"ali account 1","serviceName":"virtualMachines","kilowattHours":61.05357763407657,"co2e":0.013024032067235645,"cost":2.274481284318876,"region":"cn-hangzhou","usesAverageCPUConstant":false}]} , -{"timestamp":"2023-03-01T07:00:00.000Z","serviceEstimates":[{"cloudProvider":"AWS","accountName":"aws account 1","serviceName":"ebs","kilowattHours":0.9290466782034781,"co2e":0.0003521727952599142,"cost":1.9170203054445436,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 1","serviceName":"s3","kilowattHours":0.00007414638904524828,"co2e":3.0445100513091304e-8,"cost":2.3496108515083787,"region":"us-east-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"ec2","kilowattHours":50.74719344234976,"co2e":0.016349071069741494,"cost":1.8854738557659787,"region":"us-west-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 4","serviceName":"rds","kilowattHours":62.70792733734538,"co2e":0.02020242482649055,"cost":1.9619661070136278,"region":"us-west-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 2","serviceName":"lambda","kilowattHours":50.18018466997432,"co2e":0.019021752422662495,"cost":1.7366812626585235,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"GCP","accountName":"gcp account 3","serviceName":"computeEngine","kilowattHours":50.642678514609926,"co2e":0.024308485687012764,"cost":2.112304415112801,"region":"us-east1","usesAverageCPUConstant":false},{"cloudProvider":"AZURE","accountName":"azure account 1","serviceName":"virtualMachines","kilowattHours":50.39790599515025,"co2e":0.011339528848908806,"cost":1.844015443986478,"region":"UK South","usesAverageCPUConstant":false}]} +{"timestamp":"2023-05-01T04:00:00.000Z","serviceEstimates":[{"cloudProvider":"AWS","accountName":"aws account 1","serviceName":"ebs","kilowattHours":0.9290466782034781,"co2e":0.0003521727952599142,"cost":0.14673866469163094,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 1","serviceName":"s3","kilowattHours":0.00007414638904524828,"co2e":3.0445100513091304e-8,"cost":0.000012685458547121379,"region":"us-east-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"ec2","kilowattHours":50.74719344234976,"co2e":0.016349071069741494,"cost":6.812112945725623,"region":"us-west-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 4","serviceName":"rds","kilowattHours":62.70792733734538,"co2e":0.02020242482649055,"cost":8.41767701103773,"region":"us-west-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 2","serviceName":"lambda","kilowattHours":50.18018466997432,"co2e":0.019021752422662495,"cost":7.925730176109374,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"GCP","accountName":"gcp account 3","serviceName":"computeEngine","kilowattHours":50.642678514609926,"co2e":0.024308485687012764,"cost":10.128535702921987,"region":"us-east1","usesAverageCPUConstant":false},{"cloudProvider":"AZURE","accountName":"azure account 1","serviceName":"virtualMachines","kilowattHours":50.39790599515025,"co2e":0.011339528848908806,"cost":4.724803687045336,"region":"UK South","usesAverageCPUConstant":false},{"cloudProvider":"AliCloud","accountName":"ali account 1","serviceName":"virtualMachines","kilowattHours":50.05357763407657,"co2e":0.012024032067235646,"cost":2.474481284318876,"region":"cn-hangzhou","usesAverageCPUConstant":false}]} , -{"timestamp":"2023-02-01T07:00:00.000Z","serviceEstimates":[{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"ebs","kilowattHours":0.7903104123827822,"co2e":0.0002995821777115289,"cost":1.5941517912292598,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"s3","kilowattHours":0.0008485340026475967,"co2e":3.484148497591244e-7,"cost":2.422188298668747,"region":"us-east-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 2","serviceName":"ec2","kilowattHours":50.571988266018494,"co2e":0.01629262574369838,"cost":1.813087747561918,"region":"us-west-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"rds","kilowattHours":50.54507810450079,"co2e":0.016283956177692707,"cost":2.3595674179586856,"region":"us-west-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 4","serviceName":"lambda","kilowattHours":50.31390279587026,"co2e":0.019072440818927745,"cost":1.6209696239449094,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"GCP","accountName":"gcp account 2","serviceName":"computeEngine","kilowattHours":50.995343212272175,"co2e":0.01290182183270486,"cost":2.291652099168084,"region":"us-west2","usesAverageCPUConstant":false},{"cloudProvider":"AZURE","accountName":"azure account 1","serviceName":"virtualMachines","kilowattHours":64.71563943290937,"co2e":0.014561018872404607,"cost":2.1519820863003964,"region":"UK South","usesAverageCPUConstant":false}]} +{"timestamp":"2023-04-01T04:00:00.000Z","serviceEstimates":[{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"ebs","kilowattHours":0.7903104123827822,"co2e":0.0002995821777115289,"cost":0.1248259073798037,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"s3","kilowattHours":0.0008485340026475967,"co2e":3.484148497591244e-7,"cost":0.00014517285406630186,"region":"us-east-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 2","serviceName":"ec2","kilowattHours":50.571988266018494,"co2e":0.01629262574369838,"cost":6.788594059874325,"region":"us-west-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"rds","kilowattHours":50.54507810450079,"co2e":0.016283956177692707,"cost":6.784981740705295,"region":"us-west-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 4","serviceName":"lambda","kilowattHours":50.31390279587026,"co2e":0.019072440818927745,"cost":7.9468503412198945,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"GCP","accountName":"gcp account 2","serviceName":"computeEngine","kilowattHours":50.995343212272175,"co2e":0.01290182183270486,"cost":5.375759096960359,"region":"us-west2","usesAverageCPUConstant":false},{"cloudProvider":"AZURE","accountName":"azure account 1","serviceName":"virtualMachines","kilowattHours":64.71563943290937,"co2e":0.014561018872404607,"cost":6.067091196835253,"region":"UK South","usesAverageCPUConstant":false},{"cloudProvider":"AliCloud","accountName":"ali account 1","serviceName":"virtualMachines","kilowattHours":65.05357763407658,"co2e":0.014024032067235646,"cost":2.874481284318876,"region":"cn-hangzhou","usesAverageCPUConstant":false}]} , -{"timestamp":"2023-01-01T07:00:00.000Z","serviceEstimates":[{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"ebs","kilowattHours":0.616242116061845,"co2e":0.00023359828269344752,"cost":1.7611637388166252,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 2","serviceName":"s3","kilowattHours":0.00009916949627876193,"co2e":4.071978852802988e-8,"cost":2.4426180887131026,"region":"us-east-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 2","serviceName":"ec2","kilowattHours":50.149667680040125,"co2e":0.016156567987475487,"cost":2.2645178139778706,"region":"us-west-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 4","serviceName":"rds","kilowattHours":50.15605062459718,"co2e":0.0161586243615746,"cost":2.345203244211236,"region":"us-west-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 2","serviceName":"lambda","kilowattHours":55.97282211541847,"co2e":0.021217561706469563,"cost":1.6084626580032653,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"GCP","accountName":"gcp account 0","serviceName":"computeEngine","kilowattHours":50.58370656686491,"co2e":0.02428017915209516,"cost":2.002632458514051,"region":"us-east1","usesAverageCPUConstant":false},{"cloudProvider":"AZURE","accountName":"azure account 2","serviceName":"virtualMachines","kilowattHours":50.91095442793027,"co2e":0.01145496474628431,"cost":1.9447149842967881,"region":"UK South","usesAverageCPUConstant":false}]} +{"timestamp":"2023-03-01T05:00:00.000Z","serviceEstimates":[{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"ebs","kilowattHours":0.616242116061845,"co2e":0.00023359828269344752,"cost":0.09733261778893647,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 2","serviceName":"s3","kilowattHours":0.00009916949627876193,"co2e":4.071978852802988e-8,"cost":0.000016966578553345785,"region":"us-east-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 2","serviceName":"ec2","kilowattHours":50.149667680040125,"co2e":0.016156567987475487,"cost":6.731903328114787,"region":"us-west-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 4","serviceName":"rds","kilowattHours":50.15605062459718,"co2e":0.0161586243615746,"cost":6.732760150656084,"region":"us-west-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 2","serviceName":"lambda","kilowattHours":55.97282211541847,"co2e":0.021217561706469563,"cost":8.840650711028985,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"GCP","accountName":"gcp account 0","serviceName":"computeEngine","kilowattHours":50.58370656686491,"co2e":0.02428017915209516,"cost":10.116741313372984,"region":"us-east1","usesAverageCPUConstant":false},{"cloudProvider":"AZURE","accountName":"azure account 2","serviceName":"virtualMachines","kilowattHours":50.91095442793027,"co2e":0.01145496474628431,"cost":4.772901977618463,"region":"UK South","usesAverageCPUConstant":false},{"cloudProvider":"AliCloud","accountName":"ali account 1","serviceName":"virtualMachines","kilowattHours":70.05357763407658,"co2e":0.015024032067235646,"cost":2.974481284318876,"region":"cn-hangzhou","usesAverageCPUConstant":false}]} , -{"timestamp":"2022-12-01T07:00:00.000Z","serviceEstimates":[{"cloudProvider":"AWS","accountName":"aws account 2","serviceName":"ebs","kilowattHours":0.8445236465308483,"co2e":0.00032013273416680216,"cost":1.8371062169192403,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 2","serviceName":"s3","kilowattHours":0.0002648754924918617,"co2e":1.0875999622109836e-7,"cost":2.236483110571217,"region":"us-east-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"ec2","kilowattHours":50.37629287099671,"co2e":0.016229579145370397,"cost":2.449362850757937,"region":"us-west-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"rds","kilowattHours":50.52526081119707,"co2e":0.016277571699760927,"cost":2.302982452488826,"region":"us-west-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"lambda","kilowattHours":50.357072062609866,"co2e":0.01908880494970146,"cost":2.3300950152526614,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"GCP","accountName":"gcp account 0","serviceName":"computeEngine","kilowattHours":53.70289639150512,"co2e":0.02577739026792246,"cost":1.8981217535028845,"region":"us-east1","usesAverageCPUConstant":false},{"cloudProvider":"AZURE","accountName":"azure account 4","serviceName":"virtualMachines","kilowattHours":50.82389577797691,"co2e":0.011435376550044804,"cost":1.5108425566125716,"region":"UK South","usesAverageCPUConstant":false}]} +{"timestamp":"2023-02-01T05:00:00.000Z","serviceEstimates":[{"cloudProvider":"AWS","accountName":"aws account 2","serviceName":"ebs","kilowattHours":0.8445236465308483,"co2e":0.00032013273416680216,"cost":0.1333886392361676,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 2","serviceName":"s3","kilowattHours":0.0002648754924918617,"co2e":1.0875999622109836e-7,"cost":0.000045316665092124323,"region":"us-east-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"ec2","kilowattHours":50.37629287099671,"co2e":0.016229579145370397,"cost":6.762324643904333,"region":"us-west-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"rds","kilowattHours":50.52526081119707,"co2e":0.016277571699760927,"cost":6.782321541567054,"region":"us-west-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"lambda","kilowattHours":50.357072062609866,"co2e":0.01908880494970146,"cost":7.953668729042276,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"GCP","accountName":"gcp account 0","serviceName":"computeEngine","kilowattHours":53.70289639150512,"co2e":0.02577739026792246,"cost":10.740579278301025,"region":"us-east1","usesAverageCPUConstant":false},{"cloudProvider":"AZURE","accountName":"azure account 4","serviceName":"virtualMachines","kilowattHours":50.82389577797691,"co2e":0.011435376550044804,"cost":4.764740229185335,"region":"UK South","usesAverageCPUConstant":false},{"cloudProvider":"AliCloud","accountName":"ali account 1","serviceName":"virtualMachines","kilowattHours":72.05357763407658,"co2e":0.015524032067235646,"cost":2.874481284318876,"region":"cn-hangzhou","usesAverageCPUConstant":false}]} , -{"timestamp":"2022-11-01T06:00:00.000Z","serviceEstimates":[{"cloudProvider":"AWS","accountName":"aws account 0","serviceName":"ebs","kilowattHours":0.33278898489743725,"co2e":0.00012614998771608664,"cost":2.201012388385645,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 1","serviceName":"s3","kilowattHours":0.00098701927251199,"co2e":4.052780094476032e-7,"cost":1.645103657701107,"region":"us-east-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 4","serviceName":"ec2","kilowattHours":50.904310297531644,"co2e":0.016399688935624875,"cost":2.235818514499144,"region":"us-west-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"rds","kilowattHours":50.12852654254913,"co2e":0.016149757010633425,"cost":1.5786907055484218,"region":"us-west-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 2","serviceName":"lambda","kilowattHours":59.54349975897656,"co2e":0.022571094910135484,"cost":2.324453671310117,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"GCP","accountName":"gcp account 3","serviceName":"computeEngine","kilowattHours":63.69530490695243,"co2e":0.03057374635533717,"cost":2.4104193295098923,"region":"us-east1","usesAverageCPUConstant":false},{"cloudProvider":"AZURE","accountName":"azure account 0","serviceName":"virtualMachines","kilowattHours":65.60925208439582,"co2e":0.014762081718989059,"cost":2.4070137425368685,"region":"UK South","usesAverageCPUConstant":false}]} +{"timestamp":"2023-01-01T05:00:00.000Z","serviceEstimates":[{"cloudProvider":"AWS","accountName":"aws account 0","serviceName":"ebs","kilowattHours":0.33278898489743725,"co2e":0.00012614998771608664,"cost":0.052562494881702775,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 1","serviceName":"s3","kilowattHours":0.00098701927251199,"co2e":4.052780094476032e-7,"cost":0.00016886583726983468,"region":"us-east-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 4","serviceName":"ec2","kilowattHours":50.904310297531644,"co2e":0.016399688935624875,"cost":6.833203723177032,"region":"us-west-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"rds","kilowattHours":50.12852654254913,"co2e":0.016149757010633425,"cost":6.729065421097261,"region":"us-west-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 2","serviceName":"lambda","kilowattHours":59.54349975897656,"co2e":0.022571094910135484,"cost":9.404622879223119,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"GCP","accountName":"gcp account 3","serviceName":"computeEngine","kilowattHours":63.69530490695243,"co2e":0.03057374635533717,"cost":12.739060981390487,"region":"us-east1","usesAverageCPUConstant":false},{"cloudProvider":"AZURE","accountName":"azure account 0","serviceName":"virtualMachines","kilowattHours":65.60925208439582,"co2e":0.014762081718989059,"cost":6.150867382912108,"region":"UK South","usesAverageCPUConstant":false},{"cloudProvider":"AliCloud","accountName":"ali account 1","serviceName":"virtualMachines","kilowattHours":71.05357763407658,"co2e":0.014524032067235647,"cost":2.474481284318876,"region":"cn-hangzhou","usesAverageCPUConstant":false}]} , -{"timestamp":"2022-10-01T06:00:00.000Z","serviceEstimates":[{"cloudProvider":"AWS","accountName":"aws account 4","serviceName":"ebs","kilowattHours":0.7421818551593697,"co2e":0.00028133813365340714,"cost":2.498136748374863,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 1","serviceName":"s3","kilowattHours":0.0002180651158962681,"co2e":8.953928110793486e-8,"cost":1.7632736153406632,"region":"us-east-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 0","serviceName":"ec2","kilowattHours":54.239336047864754,"co2e":0.017474124176532442,"cost":1.9661288302344184,"region":"us-west-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 0","serviceName":"rds","kilowattHours":50.23120235816861,"co2e":0.016182835770124106,"cost":2.0142111162898986,"region":"us-west-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"lambda","kilowattHours":50.86615400739505,"co2e":0.019281782133429234,"cost":2.164622206937599,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"GCP","accountName":"gcp account 3","serviceName":"computeEngine","kilowattHours":59.19683162717123,"co2e":0.02841447918104219,"cost":1.6906306697783389,"region":"us-east1","usesAverageCPUConstant":false},{"cloudProvider":"AZURE","accountName":"azure account 2","serviceName":"virtualMachines","kilowattHours":50.27713613081028,"co2e":0.011312355629432311,"cost":1.7863670364270168,"region":"UK South","usesAverageCPUConstant":false}]} +{"timestamp":"2022-12-01T05:00:00.000Z","serviceEstimates":[{"cloudProvider":"AWS","accountName":"aws account 4","serviceName":"ebs","kilowattHours":0.7421818551593697,"co2e":0.00028133813365340714,"cost":0.11722422235558633,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 1","serviceName":"s3","kilowattHours":0.0002180651158962681,"co2e":8.953928110793486e-8,"cost":0.00003730803379497286,"region":"us-east-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 0","serviceName":"ec2","kilowattHours":54.239336047864754,"co2e":0.017474124176532442,"cost":7.280885073555185,"region":"us-west-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 0","serviceName":"rds","kilowattHours":50.23120235816861,"co2e":0.016182835770124106,"cost":6.742848237551712,"region":"us-west-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"lambda","kilowattHours":50.86615400739505,"co2e":0.019281782133429234,"cost":8.034075888928848,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"GCP","accountName":"gcp account 3","serviceName":"computeEngine","kilowattHours":59.19683162717123,"co2e":0.02841447918104219,"cost":11.839366325434247,"region":"us-east1","usesAverageCPUConstant":false},{"cloudProvider":"AZURE","accountName":"azure account 2","serviceName":"virtualMachines","kilowattHours":50.27713613081028,"co2e":0.011312355629432311,"cost":4.7134815122634635,"region":"UK South","usesAverageCPUConstant":false},{"cloudProvider":"AliCloud","accountName":"ali account 1","serviceName":"virtualMachines","kilowattHours":76.05357763407658,"co2e":0.016524032067235645,"cost":2.974481284318876,"region":"cn-hangzhou","usesAverageCPUConstant":false}]} , -{"timestamp":"2022-09-01T06:00:00.000Z","serviceEstimates":[{"cloudProvider":"AWS","accountName":"aws account 0","serviceName":"ebs","kilowattHours":0.5648293345133781,"co2e":0.00021410929100465172,"cost":2.3304265345916653,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 4","serviceName":"s3","kilowattHours":0.00005263625919629122,"co2e":2.161286911607075e-8,"cost":1.6038362843689178,"region":"us-east-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 0","serviceName":"ec2","kilowattHours":64.1169430041115,"co2e":0.02065636317680559,"cost":1.8957903016901754,"region":"us-west-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 1","serviceName":"rds","kilowattHours":50.69439982629539,"co2e":0.016332062708838108,"cost":2.3649041975938676,"region":"us-west-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 4","serviceName":"lambda","kilowattHours":50.69873776324742,"co2e":0.01921831982517644,"cost":2.1212012771669846,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"GCP","accountName":"gcp account 2","serviceName":"computeEngine","kilowattHours":50.71620115791394,"co2e":0.024343776555798693,"cost":2.0052165772682793,"region":"us-east1","usesAverageCPUConstant":false},{"cloudProvider":"AZURE","accountName":"azure account 0","serviceName":"virtualMachines","kilowattHours":50.146548832423804,"co2e":0.011282973487295355,"cost":2.4727258099898037,"region":"UK South","usesAverageCPUConstant":false}]} +{"timestamp":"2022-11-01T04:00:00.000Z","serviceEstimates":[{"cloudProvider":"AWS","accountName":"aws account 0","serviceName":"ebs","kilowattHours":0.5648293345133781,"co2e":0.00021410929100465172,"cost":0.08921220458527156,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 4","serviceName":"s3","kilowattHours":0.00005263625919629122,"co2e":2.161286911607075e-8,"cost":0.000009005362131696147,"region":"us-east-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 0","serviceName":"ec2","kilowattHours":64.1169430041115,"co2e":0.02065636317680559,"cost":8.606817990335664,"region":"us-west-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 1","serviceName":"rds","kilowattHours":50.69439982629539,"co2e":0.016332062708838108,"cost":6.805026128682545,"region":"us-west-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 4","serviceName":"lambda","kilowattHours":50.69873776324742,"co2e":0.01921831982517644,"cost":8.007633260490184,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"GCP","accountName":"gcp account 2","serviceName":"computeEngine","kilowattHours":50.71620115791394,"co2e":0.024343776555798693,"cost":10.143240231582789,"region":"us-east1","usesAverageCPUConstant":false},{"cloudProvider":"AZURE","accountName":"azure account 0","serviceName":"virtualMachines","kilowattHours":50.146548832423804,"co2e":0.011282973487295355,"cost":4.701238953039732,"region":"UK South","usesAverageCPUConstant":false},{"cloudProvider":"AliCloud","accountName":"ali account 1","serviceName":"virtualMachines","kilowattHours":60.05357763407657,"co2e":0.013524032067235646,"cost":2.474481284318876,"region":"cn-hangzhou","usesAverageCPUConstant":false}]} , -{"timestamp":"2022-08-01T06:00:00.000Z","serviceEstimates":[{"cloudProvider":"AWS","accountName":"aws account 0","serviceName":"ebs","kilowattHours":0.35269028620932774,"co2e":0.00013369395410308364,"cost":2.426536030208615,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 2","serviceName":"s3","kilowattHours":0.00007772936246220752,"co2e":3.1916298061882105e-8,"cost":1.9926068310477163,"region":"us-east-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 4","serviceName":"ec2","kilowattHours":50.00593572462313,"co2e":0.016110262294594658,"cost":1.9838511921282318,"region":"us-west-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"rds","kilowattHours":50.803461347266776,"co2e":0.016367198731864895,"cost":2.229003808663734,"region":"us-west-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 0","serviceName":"lambda","kilowattHours":52.62976125953012,"co2e":0.019950310970888823,"cost":1.6972395592555778,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"GCP","accountName":"gcp account 4","serviceName":"computeEngine","kilowattHours":64.63052755166217,"co2e":0.03102265322479784,"cost":2.30459191166866,"region":"us-east1","usesAverageCPUConstant":false},{"cloudProvider":"AZURE","accountName":"azure account 1","serviceName":"virtualMachines","kilowattHours":53.31425378242701,"co2e":0.011995707101046077,"cost":1.6930870858502807,"region":"UK South","usesAverageCPUConstant":false}]} +{"timestamp":"2022-10-01T04:00:00.000Z","serviceEstimates":[{"cloudProvider":"AWS","accountName":"aws account 0","serviceName":"ebs","kilowattHours":0.35269028620932774,"co2e":0.00013369395410308364,"cost":0.05570581420961819,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 2","serviceName":"s3","kilowattHours":0.00007772936246220752,"co2e":3.1916298061882105e-8,"cost":0.000013298457525784212,"region":"us-east-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 4","serviceName":"ec2","kilowattHours":50.00593572462313,"co2e":0.016110262294594658,"cost":6.712609289414441,"region":"us-west-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"rds","kilowattHours":50.803461347266776,"co2e":0.016367198731864895,"cost":6.81966613827704,"region":"us-west-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 0","serviceName":"lambda","kilowattHours":52.62976125953012,"co2e":0.019950310970888823,"cost":8.312629571203678,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"GCP","accountName":"gcp account 4","serviceName":"computeEngine","kilowattHours":64.63052755166217,"co2e":0.03102265322479784,"cost":12.926105510332436,"region":"us-east1","usesAverageCPUConstant":false},{"cloudProvider":"AZURE","accountName":"azure account 1","serviceName":"virtualMachines","kilowattHours":53.31425378242701,"co2e":0.011995707101046077,"cost":4.998211292102533,"region":"UK South","usesAverageCPUConstant":false},{"cloudProvider":"AliCloud","accountName":"ali account 1","serviceName":"virtualMachines","kilowattHours":55.05357763407657,"co2e":0.012524032067235647,"cost":2.274481284318876,"region":"cn-hangzhou","usesAverageCPUConstant":false}]} , -{"timestamp":"2022-07-01T06:00:00.000Z","serviceEstimates":[{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"ebs","kilowattHours":0.3793961762889928,"co2e":0.00014381732914969223,"cost":1.5660349090236223,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 2","serviceName":"s3","kilowattHours":0.0007881261941471649,"co2e":3.2361092032637913e-7,"cost":2.107104300712024,"region":"us-east-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"ec2","kilowattHours":50.4099085739953,"co2e":0.016240409015558344,"cost":1.7912671435326524,"region":"us-west-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 1","serviceName":"rds","kilowattHours":56.61135475563019,"co2e":0.01823831032755711,"cost":2.0547082075494254,"region":"us-west-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 4","serviceName":"lambda","kilowattHours":50.16319007556745,"co2e":0.019015310298755278,"cost":2.3442440999125886,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"GCP","accountName":"gcp account 1","serviceName":"computeEngine","kilowattHours":50.2235650164992,"co2e":0.02410731120791962,"cost":1.6300863324887378,"region":"us-east1","usesAverageCPUConstant":false},{"cloudProvider":"AZURE","accountName":"azure account 3","serviceName":"virtualMachines","kilowattHours":50.27485053251475,"co2e":0.011311841369815818,"cost":1.7666414199697307,"region":"UK South","usesAverageCPUConstant":false}]} +{"timestamp":"2022-09-01T04:00:00.000Z","serviceEstimates":[{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"ebs","kilowattHours":0.3793961762889928,"co2e":0.00014381732914969223,"cost":0.059923887145705096,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 2","serviceName":"s3","kilowattHours":0.0007881261941471649,"co2e":3.2361092032637913e-7,"cost":0.00013483788346932464,"region":"us-east-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"ec2","kilowattHours":50.4099085739953,"co2e":0.016240409015558344,"cost":6.7668370898159775,"region":"us-west-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 1","serviceName":"rds","kilowattHours":56.61135475563019,"co2e":0.01823831032755711,"cost":7.599295969815463,"region":"us-west-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 4","serviceName":"lambda","kilowattHours":50.16319007556745,"co2e":0.019015310298755278,"cost":7.9230459578147,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"GCP","accountName":"gcp account 1","serviceName":"computeEngine","kilowattHours":50.2235650164992,"co2e":0.02410731120791962,"cost":10.044713003299842,"region":"us-east1","usesAverageCPUConstant":false},{"cloudProvider":"AZURE","accountName":"azure account 3","serviceName":"virtualMachines","kilowattHours":50.27485053251475,"co2e":0.011311841369815818,"cost":4.713267237423258,"region":"UK South","usesAverageCPUConstant":false},{"cloudProvider":"AliCloud","accountName":"ali account 1","serviceName":"virtualMachines","kilowattHours":58.05357763407657,"co2e":0.013524032067235646,"cost":2.474481284318876,"region":"cn-hangzhou","usesAverageCPUConstant":false}]} , -{"timestamp":"2022-06-01T06:00:00.000Z","serviceEstimates":[{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"ebs","kilowattHours":0.0025963667867079376,"co2e":9.842021614705911e-7,"cost":2.110850189120251,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"s3","kilowattHours":0.00038988295545011175,"co2e":1.600890605714595e-7,"cost":1.750590801038299,"region":"us-east-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 4","serviceName":"ec2","kilowattHours":50.58419719553395,"co2e":0.016296559057893588,"cost":2.331961666410219,"region":"us-west-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 1","serviceName":"rds","kilowattHours":50.09219672002729,"co2e":0.01613805274070103,"cost":2.171231385841728,"region":"us-west-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 0","serviceName":"lambda","kilowattHours":50.34788323768369,"co2e":0.01908532175102552,"cost":2.178860527900543,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"GCP","accountName":"gcp account 2","serviceName":"computeEngine","kilowattHours":50.876621782936894,"co2e":0.02442077845580971,"cost":2.008009566294815,"region":"us-east1","usesAverageCPUConstant":false},{"cloudProvider":"AZURE","accountName":"azure account 2","serviceName":"virtualMachines","kilowattHours":69.14280006058016,"co2e":0.015557130013630535,"cost":2.159049075087805,"region":"UK South","usesAverageCPUConstant":false}]} +{"timestamp":"2022-08-01T04:00:00.000Z","serviceEstimates":[{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"ebs","kilowattHours":0.0025963667867079376,"co2e":9.842021614705911e-7,"cost":0.00041008423394607964,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"s3","kilowattHours":0.00038988295545011175,"co2e":1.600890605714595e-7,"cost":0.00006670377523810813,"region":"us-east-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 4","serviceName":"ec2","kilowattHours":50.58419719553395,"co2e":0.016296559057893588,"cost":6.790232940788996,"region":"us-west-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 1","serviceName":"rds","kilowattHours":50.09219672002729,"co2e":0.01613805274070103,"cost":6.724188641958764,"region":"us-west-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 0","serviceName":"lambda","kilowattHours":50.34788323768369,"co2e":0.01908532175102552,"cost":7.952217396260633,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"GCP","accountName":"gcp account 2","serviceName":"computeEngine","kilowattHours":50.876621782936894,"co2e":0.02442077845580971,"cost":10.17532435658738,"region":"us-east1","usesAverageCPUConstant":false},{"cloudProvider":"AZURE","accountName":"azure account 2","serviceName":"virtualMachines","kilowattHours":69.14280006058016,"co2e":0.015557130013630535,"cost":6.48213750567939,"region":"UK South","usesAverageCPUConstant":false},{"cloudProvider":"AliCloud","accountName":"ali account 1","serviceName":"virtualMachines","kilowattHours":65.05357763407658,"co2e":0.014524032067235647,"cost":2.664481284318876,"region":"cn-hangzhou","usesAverageCPUConstant":false}]} , -{"timestamp":"2022-05-01T06:00:00.000Z","serviceEstimates":[{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"ebs","kilowattHours":0.847821319053617,"co2e":0.00032138277959233556,"cost":1.8756121359502171,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"s3","kilowattHours":0.000630093974723783,"co2e":2.587216267733831e-7,"cost":2.3698572941873914,"region":"us-east-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"ec2","kilowattHours":50.10307382046631,"co2e":0.016141556983518168,"cost":2.351022985532233,"region":"us-west-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 2","serviceName":"rds","kilowattHours":50.22293041559674,"co2e":0.016180170823201556,"cost":1.5327957463127884,"region":"us-west-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"lambda","kilowattHours":68.18586584927158,"co2e":0.025847147981617528,"cost":2.205957045144824,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"GCP","accountName":"gcp account 4","serviceName":"computeEngine","kilowattHours":68.47759811709409,"co2e":0.032869247096205166,"cost":2.026647361534485,"region":"us-east1","usesAverageCPUConstant":false},{"cloudProvider":"AZURE","accountName":"azure account 1","serviceName":"virtualMachines","kilowattHours":50.5125618456214,"co2e":0.011365326415264814,"cost":1.9833785822656285,"region":"UK South","usesAverageCPUConstant":false}]} +{"timestamp":"2022-07-01T04:00:00.000Z","serviceEstimates":[{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"ebs","kilowattHours":0.847821319053617,"co2e":0.00032138277959233556,"cost":0.1339094914968065,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"s3","kilowattHours":0.000630093974723783,"co2e":2.587216267733831e-7,"cost":0.00010780067782224297,"region":"us-east-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"ec2","kilowattHours":50.10307382046631,"co2e":0.016141556983518168,"cost":6.72564874313257,"region":"us-west-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 2","serviceName":"rds","kilowattHours":50.22293041559674,"co2e":0.016180170823201556,"cost":6.741737843000649,"region":"us-west-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"lambda","kilowattHours":68.18586584927158,"co2e":0.025847147981617528,"cost":10.769644992340638,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"GCP","accountName":"gcp account 4","serviceName":"computeEngine","kilowattHours":68.47759811709409,"co2e":0.032869247096205166,"cost":13.69551962341882,"region":"us-east1","usesAverageCPUConstant":false},{"cloudProvider":"AZURE","accountName":"azure account 1","serviceName":"virtualMachines","kilowattHours":50.5125618456214,"co2e":0.011365326415264814,"cost":4.735552673027006,"region":"UK South","usesAverageCPUConstant":false},{"cloudProvider":"AliCloud","accountName":"ali account 1","serviceName":"virtualMachines","kilowattHours":58.05357763407657,"co2e":0.013224032067235646,"cost":2.554481284318876,"region":"cn-hangzhou","usesAverageCPUConstant":false}]} , -{"timestamp":"2022-04-01T06:00:00.000Z","serviceEstimates":[{"cloudProvider":"AWS","accountName":"aws account 1","serviceName":"ebs","kilowattHours":0.9119831149849784,"co2e":0.00034570452741424076,"cost":1.7466850895849164,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 1","serviceName":"s3","kilowattHours":0.00010168764085273696,"co2e":4.175375883526062e-8,"cost":2.0929755865584747,"region":"us-east-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 2","serviceName":"ec2","kilowattHours":50.13033413155082,"co2e":0.01615033935615933,"cost":2.030781896188273,"region":"us-west-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"rds","kilowattHours":53.81264913939785,"co2e":0.017336659735292387,"cost":1.7863504430322306,"region":"us-west-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 4","serviceName":"lambda","kilowattHours":63.45075892152083,"co2e":0.02405221573362198,"cost":1.8740007850011755,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"GCP","accountName":"gcp account 4","serviceName":"computeEngine","kilowattHours":50.77403726191187,"co2e":0.0243715378857177,"cost":1.7839515021969587,"region":"us-east1","usesAverageCPUConstant":false},{"cloudProvider":"AZURE","accountName":"azure account 4","serviceName":"virtualMachines","kilowattHours":50.373127573933836,"co2e":0.011333953704135112,"cost":1.8199290662411896,"region":"UK South","usesAverageCPUConstant":false}]} +{"timestamp":"2022-06-01T04:00:00.000Z","serviceEstimates":[{"cloudProvider":"AWS","accountName":"aws account 1","serviceName":"ebs","kilowattHours":0.9119831149849784,"co2e":0.00034570452741424076,"cost":0.144043553089267,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 1","serviceName":"s3","kilowattHours":0.00010168764085273696,"co2e":4.175375883526062e-8,"cost":0.000017397399514691924,"region":"us-east-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 2","serviceName":"ec2","kilowattHours":50.13033413155082,"co2e":0.01615033935615933,"cost":6.729308065066388,"region":"us-west-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"rds","kilowattHours":53.81264913939785,"co2e":0.017336659735292387,"cost":7.223608223038495,"region":"us-west-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 4","serviceName":"lambda","kilowattHours":63.45075892152083,"co2e":0.02405221573362198,"cost":10.021756555675827,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"GCP","accountName":"gcp account 4","serviceName":"computeEngine","kilowattHours":50.77403726191187,"co2e":0.0243715378857177,"cost":10.154807452382375,"region":"us-east1","usesAverageCPUConstant":false},{"cloudProvider":"AZURE","accountName":"azure account 4","serviceName":"virtualMachines","kilowattHours":50.373127573933836,"co2e":0.011333953704135112,"cost":4.722480710056297,"region":"UK South","usesAverageCPUConstant":false},{"cloudProvider":"AliCloud","accountName":"ali account 1","serviceName":"virtualMachines","kilowattHours":72.05357763407658,"co2e":0.015524032067235646,"cost":2.874481284318876,"region":"cn-hangzhou","usesAverageCPUConstant":false}]} , -{"timestamp":"2022-03-01T07:00:00.000Z","serviceEstimates":[{"cloudProvider":"AWS","accountName":"aws account 1","serviceName":"ebs","kilowattHours":0.8148394263939371,"co2e":0.00030888036652372336,"cost":1.9952062784974551,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 1","serviceName":"s3","kilowattHours":0.00010100550904187954,"co2e":4.147367005666808e-8,"cost":2.479801470978538,"region":"us-east-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 1","serviceName":"ec2","kilowattHours":50.933103987612505,"co2e":0.016408965312377156,"cost":2.014818576889102,"region":"us-west-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 2","serviceName":"rds","kilowattHours":50.92736661108625,"co2e":0.016407116918993825,"cost":2.0552869714092585,"region":"us-west-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 2","serviceName":"lambda","kilowattHours":70.47173348463286,"co2e":0.026713649540286294,"cost":1.8418849726705238,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"GCP","accountName":"gcp account 3","serviceName":"computeEngine","kilowattHours":57.666701915218034,"co2e":0.02768001691930466,"cost":1.611910606014586,"region":"us-east1","usesAverageCPUConstant":false},{"cloudProvider":"AZURE","accountName":"azure account 2","serviceName":"virtualMachines","kilowattHours":50.773157853289746,"co2e":0.011423960516990192,"cost":2.2728625664822433,"region":"UK South","usesAverageCPUConstant":false}]} +{"timestamp":"2022-05-01T04:00:00.000Z","serviceEstimates":[{"cloudProvider":"AWS","accountName":"aws account 1","serviceName":"ebs","kilowattHours":0.8148394263939371,"co2e":0.00030888036652372336,"cost":0.12870015271821808,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 1","serviceName":"s3","kilowattHours":0.00010100550904187954,"co2e":4.147367005666808e-8,"cost":0.000017280695856945034,"region":"us-east-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 1","serviceName":"ec2","kilowattHours":50.933103987612505,"co2e":0.016408965312377156,"cost":6.837068880157149,"region":"us-west-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 2","serviceName":"rds","kilowattHours":50.92736661108625,"co2e":0.016407116918993825,"cost":6.836298716247428,"region":"us-west-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 2","serviceName":"lambda","kilowattHours":70.47173348463286,"co2e":0.026713649540286294,"cost":11.130687308452623,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"GCP","accountName":"gcp account 3","serviceName":"computeEngine","kilowattHours":57.666701915218034,"co2e":0.02768001691930466,"cost":11.533340383043608,"region":"us-east1","usesAverageCPUConstant":false},{"cloudProvider":"AZURE","accountName":"azure account 2","serviceName":"virtualMachines","kilowattHours":50.773157853289746,"co2e":0.011423960516990192,"cost":4.759983548745914,"region":"UK South","usesAverageCPUConstant":false},{"cloudProvider":"AliCloud","accountName":"ali account 1","serviceName":"virtualMachines","kilowattHours":55.05357763407657,"co2e":0.013524032067235646,"cost":2.434481284318876,"region":"cn-hangzhou","usesAverageCPUConstant":false}]} ] \ No newline at end of file diff --git a/packages/client/package.json b/packages/client/package.json index 9c2c02489..ea3a6d611 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -54,7 +54,7 @@ "scripts": { "precommit": "lint-staged --no-stash && yarn test --coverage --runInBand --bail", "start": "react-scripts start", - "start-with-mock-data": "concurrently \"REACT_APP_DISABLE_CACHE=true yarn start\" \"yarn start-stub-server\"", + "start-with-mock-data": "concurrently \"REACT_APP_DISABLE_CACHE=true REACT_APP_DISABLE_FORECAST_VALIDATION=true REACT_APP_GROUP_BY=day yarn start\" \"yarn start-stub-server\"", "sonar:scan": "sonar-scanner", "build:app": "INLINE_RUNTIME_CHUNK=false react-scripts build", "build:workspace": "mkdir -p ../../dist-workspace/packages/client/build && cp -R build ../../dist-workspace/packages/client && cp package.json ../../dist-workspace/packages/client && cp index.js ../../dist-workspace/packages/client", diff --git a/packages/client/stub-server/mockData.json b/packages/client/stub-server/mockData.json index f24dca3d1..cda0a7f91 100644 --- a/packages/client/stub-server/mockData.json +++ b/packages/client/stub-server/mockData.json @@ -1,1539 +1 @@ -{ - "emissions": [ - { "region": "us-east-1", "mtPerKwHour": 0.000379069 }, - { "region": "us-east-2", "mtPerKwHour": 0.000410608 }, - { "region": "us-west-1", "mtPerKwHour": 0.000322167 }, - { "region": "us-west-2", "mtPerKwHour": 0.000322167 }, - { "region": "us-gov-east-1", "mtPerKwHour": 0.000379069 }, - { "region": "us-gov-west-1", "mtPerKwHour": 0.000322167 }, - { "region": "af-south-1", "mtPerKwHour": 0.0009006 }, - { "region": "ap-east-1", "mtPerKwHour": 0.00071 }, - { "region": "ap-south-1", "mtPerKwHour": 0.0007082 }, - { "region": "ap-northeast-3", "mtPerKwHour": 0.0004658 }, - { "region": "ap-northeast-2", "mtPerKwHour": 0.0004156 }, - { "region": "ap-southeast-1", "mtPerKwHour": 0.000408 }, - { "region": "ap-southeast-2", "mtPerKwHour": 0.00076 }, - { "region": "ap-northeast-1", "mtPerKwHour": 0.0004658 }, - { "region": "ca-central-1", "mtPerKwHour": 0.00012 }, - { "region": "cn-north-1", "mtPerKwHour": 0.0005374 }, - { "region": "cn-northwest-1", "mtPerKwHour": 0.0005374 }, - { "region": "eu-central-1", "mtPerKwHour": 0.000311 }, - { "region": "eu-west-1", "mtPerKwHour": 0.0002786 }, - { "region": "eu-west-2", "mtPerKwHour": 0.000225 }, - { "region": "eu-south-1", "mtPerKwHour": 0.0002134 }, - { "region": "eu-west-3", "mtPerKwHour": 0.0000511 }, - { "region": "eu-north-1", "mtPerKwHour": 0.0000088 }, - { "region": "me-south-1", "mtPerKwHour": 0.0005059 }, - { "region": "sa-east-1", "mtPerKwHour": 0.0000617 }, - { "region": "us-central1", "mtPerKwHour": 0.000454 }, - { "region": "us-central2", "mtPerKwHour": 0.000454 }, - { "region": "us-east1", "mtPerKwHour": 0.00048 }, - { "region": "us-east4", "mtPerKwHour": 0.000361 }, - { "region": "us-west1", "mtPerKwHour": 0.000078 }, - { "region": "us-west2", "mtPerKwHour": 0.000253 }, - { "region": "us-west3", "mtPerKwHour": 0.000533 }, - { "region": "us-west4", "mtPerKwHour": 0.000455 }, - { "region": "asia-east1", "mtPerKwHour": 0.00054 }, - { "region": "asia-east2", "mtPerKwHour": 0.000453 }, - { "region": "asia-northeast1", "mtPerKwHour": 0.000554 }, - { "region": "asia-northeast2", "mtPerKwHour": 0.000442 }, - { "region": "asia-northeast3", "mtPerKwHour": 0.000457 }, - { "region": "asia-south1", "mtPerKwHour": 0.000721 }, - { "region": "asia-southeast1", "mtPerKwHour": 0.000493 }, - { "region": "asia-southeast2", "mtPerKwHour": 0.000647 }, - { "region": "australia-southeast1", "mtPerKwHour": 0.000727 }, - { "region": "europe-north1", "mtPerKwHour": 0.000133 }, - { "region": "europe-west1", "mtPerKwHour": 0.000212 }, - { "region": "europe-west2", "mtPerKwHour": 0.000231 }, - { "region": "europe-west3", "mtPerKwHour": 0.000293 }, - { "region": "europe-west4", "mtPerKwHour": 0.00041 }, - { "region": "europe-west6", "mtPerKwHour": 0.000087 }, - { "region": "northamerica-northeast1", "mtPerKwHour": 0.000027 }, - { "region": "southamerica-east1", "mtPerKwHour": 0.000103 }, - { "region": "AP East", "mtPerKwHour": 0.00071 }, - { "region": "EU West", "mtPerKwHour": 0.0003284 }, - { "region": "IN Central", "mtPerKwHour": 0.0007082 }, - { "region": "UK South", "mtPerKwHour": 0.000225 }, - { "region": "UK West", "mtPerKwHour": 0.000225 }, - { "region": "US Central", "mtPerKwHour": 0.000426254 }, - { "region": "US East", "mtPerKwHour": 0.000379069 }, - { "region": "US South Central", "mtPerKwHour": 0.000373231 }, - { "region": "US West", "mtPerKwHour": 0.000322167 }, - { "region": "US West 2", "mtPerKwHour": 0.000322167 }, - { "region": "Unknown", "mtPerKwHour": 0.0003852304903666667 } - ], - "footprint": [ - { - "timestamp": "2023-05-01T06:00:00.000Z", - "serviceEstimates": [ - { - "cloudProvider": "AWS", - "accountName": "aws account 0", - "serviceName": "ebs", - "kilowattHours": 0.9099318685999922, - "co2e": 0.00034492696349833045, - "cost": 2.0611247334106126, - "region": "us-east-1", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AWS", - "accountName": "aws account 0", - "serviceName": "s3", - "kilowattHours": 0.000986946262424086, - "co2e": 4.0524803092142913e-7, - "cost": 2.341122015683861, - "region": "us-east-2", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AWS", - "accountName": "aws account 3", - "serviceName": "ec2", - "kilowattHours": 50.176579830111365, - "co2e": 0.016165238194127487, - "cost": 2.4154850519158613, - "region": "us-west-1", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AWS", - "accountName": "aws account 3", - "serviceName": "rds", - "kilowattHours": 50.15554852182787, - "co2e": 0.01615846260063172, - "cost": 1.8825350541114, - "region": "us-west-2", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AWS", - "accountName": "aws account 0", - "serviceName": "lambda", - "kilowattHours": 72.26453941514701, - "co2e": 0.027393246691560364, - "cost": 2.0084835420769656, - "region": "us-east-1", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "GCP", - "accountName": "gcp account 4", - "serviceName": "computeEngine", - "kilowattHours": 68.54797801901817, - "co2e": 0.03290302944912873, - "cost": 1.8688785461442674, - "region": "us-east1", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AZURE", - "accountName": "azure account 4", - "serviceName": "virtualMachines", - "kilowattHours": 50.02721869884774, - "co2e": 0.011256124207240741, - "cost": 2.2910467204304323, - "region": "UK South", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AliCloud", - "accountName": "ali account 1", - "serviceName": "virtualMachines", - "kilowattHours": 50.05357763407657, - "co2e": 0.012024032067235646, - "cost": 2.674481284318876, - "region": "cn-hangzhou", - "usesAverageCPUConstant": false - } - ] - }, - { - "timestamp": "2023-04-01T06:00:00.000Z", - "serviceEstimates": [ - { - "cloudProvider": "AWS", - "accountName": "aws account 0", - "serviceName": "ebs", - "kilowattHours": 0.9281382786569483, - "co2e": 0.00035182844915221075, - "cost": 2.225771432143803, - "region": "us-east-1", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AWS", - "accountName": "aws account 3", - "serviceName": "s3", - "kilowattHours": 0.00003270772530525257, - "co2e": 1.3430053672139147e-8, - "cost": 1.5028221506352941, - "region": "us-east-2", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AWS", - "accountName": "aws account 3", - "serviceName": "ec2", - "kilowattHours": 50.558103056454314, - "co2e": 0.016288152387388715, - "cost": 2.440745140900739, - "region": "us-west-1", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AWS", - "accountName": "aws account 3", - "serviceName": "rds", - "kilowattHours": 62.615457571422965, - "co2e": 0.02017263411941262, - "cost": 1.5955486508938537, - "region": "us-west-2", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AWS", - "accountName": "aws account 0", - "serviceName": "lambda", - "kilowattHours": 50.903421738830794, - "co2e": 0.01929590917511685, - "cost": 2.238206567880293, - "region": "us-east-1", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "GCP", - "accountName": "gcp account 1", - "serviceName": "computeEngine", - "kilowattHours": 65.28200479098749, - "co2e": 0.0050919963736970235, - "cost": 1.6177172869839958, - "region": "us-west1", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AZURE", - "accountName": "azure account 1", - "serviceName": "virtualMachines", - "kilowattHours": 61.90994721222564, - "co2e": 0.013929738122750768, - "cost": 2.2081554041299234, - "region": "UK South", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AliCloud", - "accountName": "ali account 1", - "serviceName": "virtualMachines", - "kilowattHours": 61.05357763407657, - "co2e": 0.013024032067235646, - "cost": 2.274481284318876, - "region": "cn-hangzhou", - "usesAverageCPUConstant": false - } - ] - }, - { - "timestamp": "2023-03-01T07:00:00.000Z", - "serviceEstimates": [ - { - "cloudProvider": "AWS", - "accountName": "aws account 1", - "serviceName": "ebs", - "kilowattHours": 0.9290466782034781, - "co2e": 0.0003521727952599142, - "cost": 1.9170203054445436, - "region": "us-east-1", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AWS", - "accountName": "aws account 1", - "serviceName": "s3", - "kilowattHours": 0.00007414638904524828, - "co2e": 3.0445100513091304e-8, - "cost": 2.3496108515083787, - "region": "us-east-2", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AWS", - "accountName": "aws account 3", - "serviceName": "ec2", - "kilowattHours": 50.74719344234976, - "co2e": 0.016349071069741494, - "cost": 1.8854738557659787, - "region": "us-west-1", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AWS", - "accountName": "aws account 4", - "serviceName": "rds", - "kilowattHours": 62.70792733734538, - "co2e": 0.02020242482649055, - "cost": 1.9619661070136278, - "region": "us-west-2", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AWS", - "accountName": "aws account 2", - "serviceName": "lambda", - "kilowattHours": 50.18018466997432, - "co2e": 0.019021752422662495, - "cost": 1.7366812626585235, - "region": "us-east-1", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "GCP", - "accountName": "gcp account 3", - "serviceName": "computeEngine", - "kilowattHours": 50.642678514609926, - "co2e": 0.024308485687012764, - "cost": 2.112304415112801, - "region": "us-east1", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AZURE", - "accountName": "azure account 1", - "serviceName": "virtualMachines", - "kilowattHours": 50.39790599515025, - "co2e": 0.011339528848908806, - "cost": 1.844015443986478, - "region": "UK South", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AliCloud", - "accountName": "ali account 1", - "serviceName": "virtualMachines", - "kilowattHours": 50.05357763407657, - "co2e": 0.012024032067235646, - "cost": 2.474481284318876, - "region": "cn-hangzhou", - "usesAverageCPUConstant": false - } - ] - }, - { - "timestamp": "2023-02-01T07:00:00.000Z", - "serviceEstimates": [ - { - "cloudProvider": "AWS", - "accountName": "aws account 3", - "serviceName": "ebs", - "kilowattHours": 0.7903104123827822, - "co2e": 0.0002995821777115289, - "cost": 1.5941517912292598, - "region": "us-east-1", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AWS", - "accountName": "aws account 3", - "serviceName": "s3", - "kilowattHours": 0.0008485340026475967, - "co2e": 3.484148497591244e-7, - "cost": 2.422188298668747, - "region": "us-east-2", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AWS", - "accountName": "aws account 2", - "serviceName": "ec2", - "kilowattHours": 50.571988266018494, - "co2e": 0.01629262574369838, - "cost": 1.813087747561918, - "region": "us-west-1", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AWS", - "accountName": "aws account 3", - "serviceName": "rds", - "kilowattHours": 50.54507810450079, - "co2e": 0.016283956177692707, - "cost": 2.3595674179586856, - "region": "us-west-2", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AWS", - "accountName": "aws account 4", - "serviceName": "lambda", - "kilowattHours": 50.31390279587026, - "co2e": 0.019072440818927745, - "cost": 1.6209696239449094, - "region": "us-east-1", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "GCP", - "accountName": "gcp account 2", - "serviceName": "computeEngine", - "kilowattHours": 50.995343212272175, - "co2e": 0.01290182183270486, - "cost": 2.291652099168084, - "region": "us-west2", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AZURE", - "accountName": "azure account 1", - "serviceName": "virtualMachines", - "kilowattHours": 64.71563943290937, - "co2e": 0.014561018872404607, - "cost": 2.1519820863003964, - "region": "UK South", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AliCloud", - "accountName": "ali account 1", - "serviceName": "virtualMachines", - "kilowattHours": 65.05357763407657, - "co2e": 0.014024032067235646, - "cost": 2.874481284318876, - "region": "cn-hangzhou", - "usesAverageCPUConstant": false - } - ] - }, - { - "timestamp": "2023-01-01T07:00:00.000Z", - "serviceEstimates": [ - { - "cloudProvider": "AWS", - "accountName": "aws account 3", - "serviceName": "ebs", - "kilowattHours": 0.616242116061845, - "co2e": 0.00023359828269344752, - "cost": 1.7611637388166252, - "region": "us-east-1", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AWS", - "accountName": "aws account 2", - "serviceName": "s3", - "kilowattHours": 0.00009916949627876193, - "co2e": 4.071978852802988e-8, - "cost": 2.4426180887131026, - "region": "us-east-2", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AWS", - "accountName": "aws account 2", - "serviceName": "ec2", - "kilowattHours": 50.149667680040125, - "co2e": 0.016156567987475487, - "cost": 2.2645178139778706, - "region": "us-west-1", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AWS", - "accountName": "aws account 4", - "serviceName": "rds", - "kilowattHours": 50.15605062459718, - "co2e": 0.0161586243615746, - "cost": 2.345203244211236, - "region": "us-west-2", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AWS", - "accountName": "aws account 2", - "serviceName": "lambda", - "kilowattHours": 55.97282211541847, - "co2e": 0.021217561706469563, - "cost": 1.6084626580032653, - "region": "us-east-1", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "GCP", - "accountName": "gcp account 0", - "serviceName": "computeEngine", - "kilowattHours": 50.58370656686491, - "co2e": 0.02428017915209516, - "cost": 2.002632458514051, - "region": "us-east1", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AZURE", - "accountName": "azure account 2", - "serviceName": "virtualMachines", - "kilowattHours": 50.91095442793027, - "co2e": 0.01145496474628431, - "cost": 1.9447149842967881, - "region": "UK South", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AliCloud", - "accountName": "ali account 1", - "serviceName": "virtualMachines", - "kilowattHours": 70.05357763407657, - "co2e": 0.015024032067235646, - "cost": 2.974481284318876, - "region": "cn-hangzhou", - "usesAverageCPUConstant": false - } - ] - }, - { - "timestamp": "2022-12-01T07:00:00.000Z", - "serviceEstimates": [ - { - "cloudProvider": "AWS", - "accountName": "aws account 2", - "serviceName": "ebs", - "kilowattHours": 0.8445236465308483, - "co2e": 0.00032013273416680216, - "cost": 1.8371062169192403, - "region": "us-east-1", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AWS", - "accountName": "aws account 2", - "serviceName": "s3", - "kilowattHours": 0.0002648754924918617, - "co2e": 1.0875999622109836e-7, - "cost": 2.236483110571217, - "region": "us-east-2", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AWS", - "accountName": "aws account 3", - "serviceName": "ec2", - "kilowattHours": 50.37629287099671, - "co2e": 0.016229579145370397, - "cost": 2.449362850757937, - "region": "us-west-1", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AWS", - "accountName": "aws account 3", - "serviceName": "rds", - "kilowattHours": 50.52526081119707, - "co2e": 0.016277571699760927, - "cost": 2.302982452488826, - "region": "us-west-2", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AWS", - "accountName": "aws account 3", - "serviceName": "lambda", - "kilowattHours": 50.357072062609866, - "co2e": 0.01908880494970146, - "cost": 2.3300950152526614, - "region": "us-east-1", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "GCP", - "accountName": "gcp account 0", - "serviceName": "computeEngine", - "kilowattHours": 53.70289639150512, - "co2e": 0.02577739026792246, - "cost": 1.8981217535028845, - "region": "us-east1", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AZURE", - "accountName": "azure account 4", - "serviceName": "virtualMachines", - "kilowattHours": 50.82389577797691, - "co2e": 0.011435376550044804, - "cost": 1.5108425566125716, - "region": "UK South", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AliCloud", - "accountName": "ali account 1", - "serviceName": "virtualMachines", - "kilowattHours": 72.05357763407657, - "co2e": 0.015524032067235646, - "cost": 2.874481284318876, - "region": "cn-hangzhou", - "usesAverageCPUConstant": false - } - ] - }, - { - "timestamp": "2022-11-01T06:00:00.000Z", - "serviceEstimates": [ - { - "cloudProvider": "AWS", - "accountName": "aws account 0", - "serviceName": "ebs", - "kilowattHours": 0.33278898489743725, - "co2e": 0.00012614998771608664, - "cost": 2.201012388385645, - "region": "us-east-1", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AWS", - "accountName": "aws account 1", - "serviceName": "s3", - "kilowattHours": 0.00098701927251199, - "co2e": 4.052780094476032e-7, - "cost": 1.645103657701107, - "region": "us-east-2", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AWS", - "accountName": "aws account 4", - "serviceName": "ec2", - "kilowattHours": 50.904310297531644, - "co2e": 0.016399688935624875, - "cost": 2.235818514499144, - "region": "us-west-1", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AWS", - "accountName": "aws account 3", - "serviceName": "rds", - "kilowattHours": 50.12852654254913, - "co2e": 0.016149757010633425, - "cost": 1.5786907055484218, - "region": "us-west-2", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AWS", - "accountName": "aws account 2", - "serviceName": "lambda", - "kilowattHours": 59.54349975897656, - "co2e": 0.022571094910135484, - "cost": 2.324453671310117, - "region": "us-east-1", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "GCP", - "accountName": "gcp account 3", - "serviceName": "computeEngine", - "kilowattHours": 63.69530490695243, - "co2e": 0.03057374635533717, - "cost": 2.4104193295098923, - "region": "us-east1", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AZURE", - "accountName": "azure account 0", - "serviceName": "virtualMachines", - "kilowattHours": 65.60925208439582, - "co2e": 0.014762081718989059, - "cost": 2.4070137425368685, - "region": "UK South", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AliCloud", - "accountName": "ali account 1", - "serviceName": "virtualMachines", - "kilowattHours": 71.05357763407657, - "co2e": 0.014524032067235646, - "cost": 2.474481284318876, - "region": "cn-hangzhou", - "usesAverageCPUConstant": false - } - ] - }, - { - "timestamp": "2022-10-01T06:00:00.000Z", - "serviceEstimates": [ - { - "cloudProvider": "AWS", - "accountName": "aws account 4", - "serviceName": "ebs", - "kilowattHours": 0.7421818551593697, - "co2e": 0.00028133813365340714, - "cost": 2.498136748374863, - "region": "us-east-1", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AWS", - "accountName": "aws account 1", - "serviceName": "s3", - "kilowattHours": 0.0002180651158962681, - "co2e": 8.953928110793486e-8, - "cost": 1.7632736153406632, - "region": "us-east-2", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AWS", - "accountName": "aws account 0", - "serviceName": "ec2", - "kilowattHours": 54.239336047864754, - "co2e": 0.017474124176532442, - "cost": 1.9661288302344184, - "region": "us-west-1", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AWS", - "accountName": "aws account 0", - "serviceName": "rds", - "kilowattHours": 50.23120235816861, - "co2e": 0.016182835770124106, - "cost": 2.0142111162898986, - "region": "us-west-2", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AWS", - "accountName": "aws account 3", - "serviceName": "lambda", - "kilowattHours": 50.86615400739505, - "co2e": 0.019281782133429234, - "cost": 2.164622206937599, - "region": "us-east-1", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "GCP", - "accountName": "gcp account 3", - "serviceName": "computeEngine", - "kilowattHours": 59.19683162717123, - "co2e": 0.02841447918104219, - "cost": 1.6906306697783389, - "region": "us-east1", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AZURE", - "accountName": "azure account 2", - "serviceName": "virtualMachines", - "kilowattHours": 50.27713613081028, - "co2e": 0.011312355629432311, - "cost": 1.7863670364270168, - "region": "UK South", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AliCloud", - "accountName": "ali account 1", - "serviceName": "virtualMachines", - "kilowattHours": 76.05357763407657, - "co2e": 0.016524032067235646, - "cost": 2.974481284318876, - "region": "cn-hangzhou", - "usesAverageCPUConstant": false - } - ] - }, - { - "timestamp": "2022-09-01T06:00:00.000Z", - "serviceEstimates": [ - { - "cloudProvider": "AWS", - "accountName": "aws account 0", - "serviceName": "ebs", - "kilowattHours": 0.5648293345133781, - "co2e": 0.00021410929100465172, - "cost": 2.3304265345916653, - "region": "us-east-1", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AWS", - "accountName": "aws account 4", - "serviceName": "s3", - "kilowattHours": 0.00005263625919629122, - "co2e": 2.161286911607075e-8, - "cost": 1.6038362843689178, - "region": "us-east-2", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AWS", - "accountName": "aws account 0", - "serviceName": "ec2", - "kilowattHours": 64.1169430041115, - "co2e": 0.02065636317680559, - "cost": 1.8957903016901754, - "region": "us-west-1", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AWS", - "accountName": "aws account 1", - "serviceName": "rds", - "kilowattHours": 50.69439982629539, - "co2e": 0.016332062708838108, - "cost": 2.3649041975938676, - "region": "us-west-2", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AWS", - "accountName": "aws account 4", - "serviceName": "lambda", - "kilowattHours": 50.69873776324742, - "co2e": 0.01921831982517644, - "cost": 2.1212012771669846, - "region": "us-east-1", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "GCP", - "accountName": "gcp account 2", - "serviceName": "computeEngine", - "kilowattHours": 50.71620115791394, - "co2e": 0.024343776555798693, - "cost": 2.0052165772682793, - "region": "us-east1", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AZURE", - "accountName": "azure account 0", - "serviceName": "virtualMachines", - "kilowattHours": 50.146548832423804, - "co2e": 0.011282973487295355, - "cost": 2.4727258099898037, - "region": "UK South", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AliCloud", - "accountName": "ali account 1", - "serviceName": "virtualMachines", - "kilowattHours": 60.05357763407657, - "co2e": 0.013524032067235646, - "cost": 2.474481284318876, - "region": "cn-hangzhou", - "usesAverageCPUConstant": false - } - ] - }, - { - "timestamp": "2022-08-01T06:00:00.000Z", - "serviceEstimates": [ - { - "cloudProvider": "AWS", - "accountName": "aws account 0", - "serviceName": "ebs", - "kilowattHours": 0.35269028620932774, - "co2e": 0.00013369395410308364, - "cost": 2.426536030208615, - "region": "us-east-1", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AWS", - "accountName": "aws account 2", - "serviceName": "s3", - "kilowattHours": 0.00007772936246220752, - "co2e": 3.1916298061882105e-8, - "cost": 1.9926068310477163, - "region": "us-east-2", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AWS", - "accountName": "aws account 4", - "serviceName": "ec2", - "kilowattHours": 50.00593572462313, - "co2e": 0.016110262294594658, - "cost": 1.9838511921282318, - "region": "us-west-1", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AWS", - "accountName": "aws account 3", - "serviceName": "rds", - "kilowattHours": 50.803461347266776, - "co2e": 0.016367198731864895, - "cost": 2.229003808663734, - "region": "us-west-2", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AWS", - "accountName": "aws account 0", - "serviceName": "lambda", - "kilowattHours": 52.62976125953012, - "co2e": 0.019950310970888823, - "cost": 1.6972395592555778, - "region": "us-east-1", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "GCP", - "accountName": "gcp account 4", - "serviceName": "computeEngine", - "kilowattHours": 64.63052755166217, - "co2e": 0.03102265322479784, - "cost": 2.30459191166866, - "region": "us-east1", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AZURE", - "accountName": "azure account 1", - "serviceName": "virtualMachines", - "kilowattHours": 53.31425378242701, - "co2e": 0.011995707101046077, - "cost": 1.6930870858502807, - "region": "UK South", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AliCloud", - "accountName": "ali account 1", - "serviceName": "virtualMachines", - "kilowattHours": 55.05357763407657, - "co2e": 0.012524032067235646, - "cost": 2.274481284318876, - "region": "cn-hangzhou", - "usesAverageCPUConstant": false - } - ] - }, - { - "timestamp": "2022-07-01T06:00:00.000Z", - "serviceEstimates": [ - { - "cloudProvider": "AWS", - "accountName": "aws account 3", - "serviceName": "ebs", - "kilowattHours": 0.3793961762889928, - "co2e": 0.00014381732914969223, - "cost": 1.5660349090236223, - "region": "us-east-1", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AWS", - "accountName": "aws account 2", - "serviceName": "s3", - "kilowattHours": 0.0007881261941471649, - "co2e": 3.2361092032637913e-7, - "cost": 2.107104300712024, - "region": "us-east-2", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AWS", - "accountName": "aws account 3", - "serviceName": "ec2", - "kilowattHours": 50.4099085739953, - "co2e": 0.016240409015558344, - "cost": 1.7912671435326524, - "region": "us-west-1", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AWS", - "accountName": "aws account 1", - "serviceName": "rds", - "kilowattHours": 56.61135475563019, - "co2e": 0.01823831032755711, - "cost": 2.0547082075494254, - "region": "us-west-2", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AWS", - "accountName": "aws account 4", - "serviceName": "lambda", - "kilowattHours": 50.16319007556745, - "co2e": 0.019015310298755278, - "cost": 2.3442440999125886, - "region": "us-east-1", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "GCP", - "accountName": "gcp account 1", - "serviceName": "computeEngine", - "kilowattHours": 50.2235650164992, - "co2e": 0.02410731120791962, - "cost": 1.6300863324887378, - "region": "us-east1", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AZURE", - "accountName": "azure account 3", - "serviceName": "virtualMachines", - "kilowattHours": 50.27485053251475, - "co2e": 0.011311841369815818, - "cost": 1.7666414199697307, - "region": "UK South", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AliCloud", - "accountName": "ali account 1", - "serviceName": "virtualMachines", - "kilowattHours": 58.05357763407657, - "co2e": 0.013524032067235646, - "cost": 2.474481284318876, - "region": "cn-hangzhou", - "usesAverageCPUConstant": false - } - ] - }, - { - "timestamp": "2022-06-01T06:00:00.000Z", - "serviceEstimates": [ - { - "cloudProvider": "AWS", - "accountName": "aws account 3", - "serviceName": "ebs", - "kilowattHours": 0.0025963667867079376, - "co2e": 9.842021614705911e-7, - "cost": 2.110850189120251, - "region": "us-east-1", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AWS", - "accountName": "aws account 3", - "serviceName": "s3", - "kilowattHours": 0.00038988295545011175, - "co2e": 1.600890605714595e-7, - "cost": 1.750590801038299, - "region": "us-east-2", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AWS", - "accountName": "aws account 4", - "serviceName": "ec2", - "kilowattHours": 50.58419719553395, - "co2e": 0.016296559057893588, - "cost": 2.331961666410219, - "region": "us-west-1", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AWS", - "accountName": "aws account 1", - "serviceName": "rds", - "kilowattHours": 50.09219672002729, - "co2e": 0.01613805274070103, - "cost": 2.171231385841728, - "region": "us-west-2", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AWS", - "accountName": "aws account 0", - "serviceName": "lambda", - "kilowattHours": 50.34788323768369, - "co2e": 0.01908532175102552, - "cost": 2.178860527900543, - "region": "us-east-1", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "GCP", - "accountName": "gcp account 2", - "serviceName": "computeEngine", - "kilowattHours": 50.876621782936894, - "co2e": 0.02442077845580971, - "cost": 2.008009566294815, - "region": "us-east1", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AZURE", - "accountName": "azure account 2", - "serviceName": "virtualMachines", - "kilowattHours": 69.14280006058016, - "co2e": 0.015557130013630535, - "cost": 2.159049075087805, - "region": "UK South", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AliCloud", - "accountName": "ali account 1", - "serviceName": "virtualMachines", - "kilowattHours": 65.05357763407657, - "co2e": 0.014524032067235646, - "cost": 2.664481284318876, - "region": "cn-hangzhou", - "usesAverageCPUConstant": false - } - ] - }, - { - "timestamp": "2022-05-01T06:00:00.000Z", - "serviceEstimates": [ - { - "cloudProvider": "AWS", - "accountName": "aws account 3", - "serviceName": "ebs", - "kilowattHours": 0.847821319053617, - "co2e": 0.00032138277959233556, - "cost": 1.8756121359502171, - "region": "us-east-1", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AWS", - "accountName": "aws account 3", - "serviceName": "s3", - "kilowattHours": 0.000630093974723783, - "co2e": 2.587216267733831e-7, - "cost": 2.3698572941873914, - "region": "us-east-2", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AWS", - "accountName": "aws account 3", - "serviceName": "ec2", - "kilowattHours": 50.10307382046631, - "co2e": 0.016141556983518168, - "cost": 2.351022985532233, - "region": "us-west-1", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AWS", - "accountName": "aws account 2", - "serviceName": "rds", - "kilowattHours": 50.22293041559674, - "co2e": 0.016180170823201556, - "cost": 1.5327957463127884, - "region": "us-west-2", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AWS", - "accountName": "aws account 3", - "serviceName": "lambda", - "kilowattHours": 68.18586584927158, - "co2e": 0.025847147981617528, - "cost": 2.205957045144824, - "region": "us-east-1", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "GCP", - "accountName": "gcp account 4", - "serviceName": "computeEngine", - "kilowattHours": 68.47759811709409, - "co2e": 0.032869247096205166, - "cost": 2.026647361534485, - "region": "us-east1", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AZURE", - "accountName": "azure account 1", - "serviceName": "virtualMachines", - "kilowattHours": 50.5125618456214, - "co2e": 0.011365326415264814, - "cost": 1.9833785822656285, - "region": "UK South", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AliCloud", - "accountName": "ali account 1", - "serviceName": "virtualMachines", - "kilowattHours": 58.05357763407657, - "co2e": 0.013224032067235646, - "cost": 2.554481284318876, - "region": "cn-hangzhou", - "usesAverageCPUConstant": false - } - ] - }, - { - "timestamp": "2022-04-01T06:00:00.000Z", - "serviceEstimates": [ - { - "cloudProvider": "AWS", - "accountName": "aws account 1", - "serviceName": "ebs", - "kilowattHours": 0.9119831149849784, - "co2e": 0.00034570452741424076, - "cost": 1.7466850895849164, - "region": "us-east-1", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AWS", - "accountName": "aws account 1", - "serviceName": "s3", - "kilowattHours": 0.00010168764085273696, - "co2e": 4.175375883526062e-8, - "cost": 2.0929755865584747, - "region": "us-east-2", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AWS", - "accountName": "aws account 2", - "serviceName": "ec2", - "kilowattHours": 50.13033413155082, - "co2e": 0.01615033935615933, - "cost": 2.030781896188273, - "region": "us-west-1", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AWS", - "accountName": "aws account 3", - "serviceName": "rds", - "kilowattHours": 53.81264913939785, - "co2e": 0.017336659735292387, - "cost": 1.7863504430322306, - "region": "us-west-2", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AWS", - "accountName": "aws account 4", - "serviceName": "lambda", - "kilowattHours": 63.45075892152083, - "co2e": 0.02405221573362198, - "cost": 1.8740007850011755, - "region": "us-east-1", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "GCP", - "accountName": "gcp account 4", - "serviceName": "computeEngine", - "kilowattHours": 50.77403726191187, - "co2e": 0.0243715378857177, - "cost": 1.7839515021969587, - "region": "us-east1", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AZURE", - "accountName": "azure account 4", - "serviceName": "virtualMachines", - "kilowattHours": 50.373127573933836, - "co2e": 0.011333953704135112, - "cost": 1.8199290662411896, - "region": "UK South", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AliCloud", - "accountName": "ali account 1", - "serviceName": "virtualMachines", - "kilowattHours": 72.05357763407657, - "co2e": 0.015524032067235646, - "cost": 2.874481284318876, - "region": "cn-hangzhou", - "usesAverageCPUConstant": false - } - ] - }, - { - "timestamp": "2022-03-01T07:00:00.000Z", - "serviceEstimates": [ - { - "cloudProvider": "AWS", - "accountName": "aws account 1", - "serviceName": "ebs", - "kilowattHours": 0.8148394263939371, - "co2e": 0.00030888036652372336, - "cost": 1.9952062784974551, - "region": "us-east-1", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AWS", - "accountName": "aws account 1", - "serviceName": "s3", - "kilowattHours": 0.00010100550904187954, - "co2e": 4.147367005666808e-8, - "cost": 2.479801470978538, - "region": "us-east-2", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AWS", - "accountName": "aws account 1", - "serviceName": "ec2", - "kilowattHours": 50.933103987612505, - "co2e": 0.016408965312377156, - "cost": 2.014818576889102, - "region": "us-west-1", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AWS", - "accountName": "aws account 2", - "serviceName": "rds", - "kilowattHours": 50.92736661108625, - "co2e": 0.016407116918993825, - "cost": 2.0552869714092585, - "region": "us-west-2", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AWS", - "accountName": "aws account 2", - "serviceName": "lambda", - "kilowattHours": 70.47173348463286, - "co2e": 0.026713649540286294, - "cost": 1.8418849726705238, - "region": "us-east-1", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "GCP", - "accountName": "gcp account 3", - "serviceName": "computeEngine", - "kilowattHours": 57.666701915218034, - "co2e": 0.02768001691930466, - "cost": 1.611910606014586, - "region": "us-east1", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AZURE", - "accountName": "azure account 2", - "serviceName": "virtualMachines", - "kilowattHours": 50.773157853289746, - "co2e": 0.011423960516990192, - "cost": 2.2728625664822433, - "region": "UK South", - "usesAverageCPUConstant": false - }, - { - "cloudProvider": "AliCloud", - "accountName": "ali account 1", - "serviceName": "virtualMachines", - "kilowattHours": 55.05357763407657, - "co2e": 0.013524032067235646, - "cost": 2.434481284318876, - "region": "cn-hangzhou", - "usesAverageCPUConstant": false - } - ] - } - ], - "recommendations": [ - { - "cloudProvider": "AWS", - "accountId": "aws account 0", - "accountName": "aws account 0", - "region": "us-west-1", - "recommendationType": "Modify", - "instanceName": "example-instance-5", - "recommendationDetail": "Modify instance: example-instance-5.", - "resourceId": "i-0f12345678912b12I", - "kilowattHourSavings": 116.513, - "costSavings": 3.611, - "co2eSavings": 0.037536643671 - }, - { - "cloudProvider": "AWS", - "accountId": "aws account 1", - "accountName": "aws account 1", - "region": "us-east-2", - "recommendationType": "Modify", - "instanceName": "example-instance", - "recommendationDetail": "Modify instance: example-instance.", - "resourceId": "i-0f12345678912b12I", - "kilowattHourSavings": 114.978, - "costSavings": 13.506, - "co2eSavings": 0.047210886624 - }, - { - "cloudProvider": "AWS", - "accountId": "aws account 2", - "accountName": "aws account 2", - "region": "us-east-1", - "recommendationType": "Modify", - "instanceName": "example-instance-2", - "recommendationDetail": "Modify instance: example-instance-2.", - "resourceId": "i-0f12345678912b12I", - "kilowattHourSavings": 18.419, - "costSavings": 5.667, - "co2eSavings": 0.0069820719110000005 - }, - { - "cloudProvider": "AWS", - "accountId": "aws account 3", - "accountName": "aws account 3", - "region": "us-west-2", - "recommendationType": "Delete", - "instanceName": "example-instance", - "recommendationDetail": "Delete instance: example-instance.", - "resourceId": "i-0f12345678912b12I", - "kilowattHourSavings": 11.195, - "costSavings": 4.442, - "co2eSavings": 0.003606659565 - }, - { - "cloudProvider": "AWS", - "accountId": "aws account 4", - "accountName": "aws account 4", - "region": "us-west-1", - "recommendationType": "Delete", - "instanceName": "example-instance", - "recommendationDetail": "Delete instance: example-instance.", - "resourceId": "i-0f12345678912b12I", - "kilowattHourSavings": 111.717, - "costSavings": 5.788, - "co2eSavings": 0.035991530739 - }, - { - "cloudProvider": "GCP", - "accountId": "gcp account 0", - "accountName": "gcp account 0", - "region": "us-west1", - "recommendationType": "DELETE_IMAGE", - "instanceName": "test-instance-1", - "recommendationDetail": "Save cost by performing a DELETE_IMAGE for instance: test-instance-1.", - "resourceId": 6906976106869124000, - "kilowattHourSavings": 12.081, - "costSavings": 1.314, - "co2eSavings": 0.0009423179999999999 - }, - { - "cloudProvider": "GCP", - "accountId": "gcp account 1", - "accountName": "gcp account 1", - "region": "us-west1", - "recommendationType": "SNAPSHOT_AND_DELETE_DISK", - "instanceName": "test-instance-3", - "recommendationDetail": "Save cost by performing a SNAPSHOT_AND_DELETE_DISK for instance: test-instance-3.", - "resourceId": 4256745502855943000, - "kilowattHourSavings": 18.742, - "costSavings": 4.549, - "co2eSavings": 0.001461876 - }, - { - "cloudProvider": "GCP", - "accountId": "gcp account 2", - "accountName": "gcp account 2", - "region": "us-west1", - "recommendationType": "CHANGE_MACHINE_TYPE", - "instanceName": "test-instance-10", - "recommendationDetail": "Save cost by performing a CHANGE_MACHINE_TYPE for instance: test-instance-10.", - "resourceId": 9625521363699055000, - "kilowattHourSavings": 16.989, - "costSavings": 8.165, - "co2eSavings": 0.001325142 - }, - { - "cloudProvider": "GCP", - "accountId": "gcp account 3", - "accountName": "gcp account 3", - "region": "us-east1", - "recommendationType": "DELETE_ADDRESS", - "instanceName": "test-instance-8", - "recommendationDetail": "Save cost by performing a DELETE_ADDRESS for instance: test-instance-8.", - "resourceId": 9351508929180877000, - "kilowattHourSavings": 19.946, - "costSavings": 5.2, - "co2eSavings": 0.00957408 - }, - { - "cloudProvider": "GCP", - "accountId": "gcp account 4", - "accountName": "gcp account 4", - "region": "us-west2", - "recommendationType": "DELETE_DISK", - "instanceName": "test-instance-14", - "recommendationDetail": "Save cost by performing a DELETE_DISK for instance: test-instance-14.", - "resourceId": 7840914330904416000, - "kilowattHourSavings": 18.782, - "costSavings": 8.359, - "co2eSavings": 0.0047518460000000005 - }, - { - "cloudProvider": "GCP", - "accountId": "gcp account 2", - "accountName": "gcp account 2", - "region": "us-east1", - "recommendationType": "STOP_VM", - "instanceName": "test-instance-9", - "recommendationDetail": "Save cost by performing a STOP_VM for instance: test-instance-9.", - "resourceId": 8928403120086348000, - "kilowattHourSavings": 116.483, - "costSavings": 8.409, - "co2eSavings": 0.055911840000000004 - }, - { - "cloudProvider": "GCP", - "accountId": "gcp account 4", - "accountName": "gcp account 4", - "region": "us-east1", - "recommendationType": "STOP_VM", - "instanceName": "test-instance-9", - "recommendationDetail": "Save cost by performing a STOP_VM for instance: test-instance-9.", - "resourceId": 8928403120086348000, - "kilowattHourSavings": 0.0001, - "costSavings": 0.0002, - "co2eSavings": 4.8000000000000006e-8 - }, - { - "cloudProvider": "GCP", - "accountId": "gcp account 4", - "accountName": "gcp account 4", - "region": "us-east1", - "recommendationType": "STOP_VM", - "instanceName": "test-instance-9", - "recommendationDetail": "Save cost by performing a STOP_VM for instance: test-instance-9.", - "resourceId": 8928403120086348000, - "kilowattHourSavings": 0, - "costSavings": 0, - "co2eSavings": 0 - }, - { - "cloudProvider": "GCP", - "accountId": "gcp account 4", - "accountName": "gcp account 4", - "region": "us-east1", - "recommendationType": "STOP_VM", - "instanceName": "test-instance-9", - "recommendationDetail": "Save cost by performing a STOP_VM for instance: test-instance-9.", - "resourceId": 8928403120086348000, - "kilowattHourSavings": 0.001, - "costSavings": 0.001, - "co2eSavings": 4.800000000000001e-7 - }, - { - "cloudProvider": "GCP", - "accountId": "gcp account 4", - "accountName": "gcp account 4", - "region": "us-east1", - "recommendationType": "STOP_VM", - "instanceName": "test-instance-9", - "recommendationDetail": "Save cost by performing a STOP_VM for instance: test-instance-9.", - "resourceId": 8928403120086348000, - "kilowattHourSavings": 0.002, - "costSavings": 0.002, - "co2eSavings": 9.600000000000001e-7 - } - ] -} +{"emissions":[{"region":"us-east-1","mtPerKwHour":0.000379069},{"region":"us-east-2","mtPerKwHour":0.000410608},{"region":"us-west-1","mtPerKwHour":0.000322167},{"region":"us-west-2","mtPerKwHour":0.000322167},{"region":"us-gov-east-1","mtPerKwHour":0.000379069},{"region":"us-gov-west-1","mtPerKwHour":0.000322167},{"region":"af-south-1","mtPerKwHour":0.0009006},{"region":"ap-east-1","mtPerKwHour":0.00071},{"region":"ap-south-1","mtPerKwHour":0.0007082},{"region":"ap-northeast-3","mtPerKwHour":0.0004658},{"region":"ap-northeast-2","mtPerKwHour":0.0004156},{"region":"ap-southeast-1","mtPerKwHour":0.000408},{"region":"ap-southeast-2","mtPerKwHour":0.00076},{"region":"ap-northeast-1","mtPerKwHour":0.0004658},{"region":"ca-central-1","mtPerKwHour":0.00012},{"region":"cn-north-1","mtPerKwHour":0.0005374},{"region":"cn-northwest-1","mtPerKwHour":0.0005374},{"region":"eu-central-1","mtPerKwHour":0.000311},{"region":"eu-west-1","mtPerKwHour":0.0002786},{"region":"eu-west-2","mtPerKwHour":0.000225},{"region":"eu-south-1","mtPerKwHour":0.0002134},{"region":"eu-west-3","mtPerKwHour":0.0000511},{"region":"eu-north-1","mtPerKwHour":0.0000088},{"region":"me-south-1","mtPerKwHour":0.0005059},{"region":"sa-east-1","mtPerKwHour":0.0000617},{"region":"us-central1","mtPerKwHour":0.000454},{"region":"us-central2","mtPerKwHour":0.000454},{"region":"us-east1","mtPerKwHour":0.00048},{"region":"us-east4","mtPerKwHour":0.000361},{"region":"us-west1","mtPerKwHour":0.000078},{"region":"us-west2","mtPerKwHour":0.000253},{"region":"us-west3","mtPerKwHour":0.000533},{"region":"us-west4","mtPerKwHour":0.000455},{"region":"asia-east1","mtPerKwHour":0.00054},{"region":"asia-east2","mtPerKwHour":0.000453},{"region":"asia-northeast1","mtPerKwHour":0.000554},{"region":"asia-northeast2","mtPerKwHour":0.000442},{"region":"asia-northeast3","mtPerKwHour":0.000457},{"region":"asia-south1","mtPerKwHour":0.000721},{"region":"asia-southeast1","mtPerKwHour":0.000493},{"region":"asia-southeast2","mtPerKwHour":0.000647},{"region":"australia-southeast1","mtPerKwHour":0.000727},{"region":"europe-north1","mtPerKwHour":0.000133},{"region":"europe-west1","mtPerKwHour":0.000212},{"region":"europe-west2","mtPerKwHour":0.000231},{"region":"europe-west3","mtPerKwHour":0.000293},{"region":"europe-west4","mtPerKwHour":0.00041},{"region":"europe-west6","mtPerKwHour":0.000087},{"region":"northamerica-northeast1","mtPerKwHour":0.000027},{"region":"southamerica-east1","mtPerKwHour":0.000103},{"region":"AP East","mtPerKwHour":0.00071},{"region":"EU West","mtPerKwHour":0.0003284},{"region":"IN Central","mtPerKwHour":0.0007082},{"region":"UK South","mtPerKwHour":0.000225},{"region":"UK West","mtPerKwHour":0.000225},{"region":"US Central","mtPerKwHour":0.000426254},{"region":"US East","mtPerKwHour":0.000379069},{"region":"US South Central","mtPerKwHour":0.000373231},{"region":"US West","mtPerKwHour":0.000322167},{"region":"US West 2","mtPerKwHour":0.000322167},{"region":"Unknown","mtPerKwHour":0.0003852304903666667}],"footprint":[{"timestamp":"2023-07-01T04:00:00.000Z","serviceEstimates":[{"cloudProvider":"AWS","accountName":"aws account 0","serviceName":"ebs","kilowattHours":0.9099318685999922,"co2e":0.00034492696349833045,"cost":0.14371956812430436,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 0","serviceName":"s3","kilowattHours":0.000986946262424086,"co2e":4.0524803092142913e-7,"cost":0.00016885334621726214,"region":"us-east-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"ec2","kilowattHours":50.176579830111365,"co2e":0.016165238194127487,"cost":6.7355159142197865,"region":"us-west-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"rds","kilowattHours":50.15554852182787,"co2e":0.01615846260063172,"cost":6.732692750263217,"region":"us-west-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 0","serviceName":"lambda","kilowattHours":72.26453941514701,"co2e":0.027393246691560364,"cost":11.413852788150153,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"GCP","accountName":"gcp account 4","serviceName":"computeEngine","kilowattHours":68.54797801901817,"co2e":0.03290302944912873,"cost":13.709595603803638,"region":"us-east1","usesAverageCPUConstant":false},{"cloudProvider":"AZURE","accountName":"azure account 4","serviceName":"virtualMachines","kilowattHours":50.02721869884774,"co2e":0.011256124207240741,"cost":4.690051753016976,"region":"UK South","usesAverageCPUConstant":false},{"cloudProvider":"AliCloud","accountName":"ali account 1","serviceName":"virtualMachines","kilowattHours":50.05357763407657,"co2e":0.012024032067235646,"cost":2.674481284318876,"region":"cn-hangzhou","usesAverageCPUConstant":false}]},{"timestamp":"2023-06-01T04:00:00.000Z","serviceEstimates":[{"cloudProvider":"AWS","accountName":"aws account 0","serviceName":"ebs","kilowattHours":0.9281382786569483,"co2e":0.00035182844915221075,"cost":0.14659518714675449,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"s3","kilowattHours":0.00003270772530525257,"co2e":1.3430053672139147e-8,"cost":0.000005595855696724645,"region":"us-east-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"ec2","kilowattHours":50.558103056454314,"co2e":0.016288152387388715,"cost":6.786730161411965,"region":"us-west-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"rds","kilowattHours":62.615457571422965,"co2e":0.02017263411941262,"cost":8.405264216421926,"region":"us-west-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 0","serviceName":"lambda","kilowattHours":50.903421738830794,"co2e":0.01929590917511685,"cost":8.039962156298689,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"GCP","accountName":"gcp account 1","serviceName":"computeEngine","kilowattHours":65.28200479098749,"co2e":0.0050919963736970235,"cost":2.121665155707093,"region":"us-west1","usesAverageCPUConstant":false},{"cloudProvider":"AZURE","accountName":"azure account 1","serviceName":"virtualMachines","kilowattHours":61.90994721222564,"co2e":0.013929738122750768,"cost":5.804057551146154,"region":"UK South","usesAverageCPUConstant":false},{"cloudProvider":"AliCloud","accountName":"ali account 1","serviceName":"virtualMachines","kilowattHours":61.05357763407657,"co2e":0.013024032067235645,"cost":2.274481284318876,"region":"cn-hangzhou","usesAverageCPUConstant":false}]},{"timestamp":"2023-05-01T04:00:00.000Z","serviceEstimates":[{"cloudProvider":"AWS","accountName":"aws account 1","serviceName":"ebs","kilowattHours":0.9290466782034781,"co2e":0.0003521727952599142,"cost":0.14673866469163094,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 1","serviceName":"s3","kilowattHours":0.00007414638904524828,"co2e":3.0445100513091304e-8,"cost":0.000012685458547121379,"region":"us-east-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"ec2","kilowattHours":50.74719344234976,"co2e":0.016349071069741494,"cost":6.812112945725623,"region":"us-west-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 4","serviceName":"rds","kilowattHours":62.70792733734538,"co2e":0.02020242482649055,"cost":8.41767701103773,"region":"us-west-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 2","serviceName":"lambda","kilowattHours":50.18018466997432,"co2e":0.019021752422662495,"cost":7.925730176109374,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"GCP","accountName":"gcp account 3","serviceName":"computeEngine","kilowattHours":50.642678514609926,"co2e":0.024308485687012764,"cost":10.128535702921987,"region":"us-east1","usesAverageCPUConstant":false},{"cloudProvider":"AZURE","accountName":"azure account 1","serviceName":"virtualMachines","kilowattHours":50.39790599515025,"co2e":0.011339528848908806,"cost":4.724803687045336,"region":"UK South","usesAverageCPUConstant":false},{"cloudProvider":"AliCloud","accountName":"ali account 1","serviceName":"virtualMachines","kilowattHours":50.05357763407657,"co2e":0.012024032067235646,"cost":2.474481284318876,"region":"cn-hangzhou","usesAverageCPUConstant":false}]},{"timestamp":"2023-04-01T04:00:00.000Z","serviceEstimates":[{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"ebs","kilowattHours":0.7903104123827822,"co2e":0.0002995821777115289,"cost":0.1248259073798037,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"s3","kilowattHours":0.0008485340026475967,"co2e":3.484148497591244e-7,"cost":0.00014517285406630186,"region":"us-east-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 2","serviceName":"ec2","kilowattHours":50.571988266018494,"co2e":0.01629262574369838,"cost":6.788594059874325,"region":"us-west-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"rds","kilowattHours":50.54507810450079,"co2e":0.016283956177692707,"cost":6.784981740705295,"region":"us-west-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 4","serviceName":"lambda","kilowattHours":50.31390279587026,"co2e":0.019072440818927745,"cost":7.9468503412198945,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"GCP","accountName":"gcp account 2","serviceName":"computeEngine","kilowattHours":50.995343212272175,"co2e":0.01290182183270486,"cost":5.375759096960359,"region":"us-west2","usesAverageCPUConstant":false},{"cloudProvider":"AZURE","accountName":"azure account 1","serviceName":"virtualMachines","kilowattHours":64.71563943290937,"co2e":0.014561018872404607,"cost":6.067091196835253,"region":"UK South","usesAverageCPUConstant":false},{"cloudProvider":"AliCloud","accountName":"ali account 1","serviceName":"virtualMachines","kilowattHours":65.05357763407658,"co2e":0.014024032067235646,"cost":2.874481284318876,"region":"cn-hangzhou","usesAverageCPUConstant":false}]},{"timestamp":"2023-03-01T05:00:00.000Z","serviceEstimates":[{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"ebs","kilowattHours":0.616242116061845,"co2e":0.00023359828269344752,"cost":0.09733261778893647,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 2","serviceName":"s3","kilowattHours":0.00009916949627876193,"co2e":4.071978852802988e-8,"cost":0.000016966578553345785,"region":"us-east-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 2","serviceName":"ec2","kilowattHours":50.149667680040125,"co2e":0.016156567987475487,"cost":6.731903328114787,"region":"us-west-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 4","serviceName":"rds","kilowattHours":50.15605062459718,"co2e":0.0161586243615746,"cost":6.732760150656084,"region":"us-west-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 2","serviceName":"lambda","kilowattHours":55.97282211541847,"co2e":0.021217561706469563,"cost":8.840650711028985,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"GCP","accountName":"gcp account 0","serviceName":"computeEngine","kilowattHours":50.58370656686491,"co2e":0.02428017915209516,"cost":10.116741313372984,"region":"us-east1","usesAverageCPUConstant":false},{"cloudProvider":"AZURE","accountName":"azure account 2","serviceName":"virtualMachines","kilowattHours":50.91095442793027,"co2e":0.01145496474628431,"cost":4.772901977618463,"region":"UK South","usesAverageCPUConstant":false},{"cloudProvider":"AliCloud","accountName":"ali account 1","serviceName":"virtualMachines","kilowattHours":70.05357763407658,"co2e":0.015024032067235646,"cost":2.974481284318876,"region":"cn-hangzhou","usesAverageCPUConstant":false}]},{"timestamp":"2023-02-01T05:00:00.000Z","serviceEstimates":[{"cloudProvider":"AWS","accountName":"aws account 2","serviceName":"ebs","kilowattHours":0.8445236465308483,"co2e":0.00032013273416680216,"cost":0.1333886392361676,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 2","serviceName":"s3","kilowattHours":0.0002648754924918617,"co2e":1.0875999622109836e-7,"cost":0.000045316665092124323,"region":"us-east-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"ec2","kilowattHours":50.37629287099671,"co2e":0.016229579145370397,"cost":6.762324643904333,"region":"us-west-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"rds","kilowattHours":50.52526081119707,"co2e":0.016277571699760927,"cost":6.782321541567054,"region":"us-west-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"lambda","kilowattHours":50.357072062609866,"co2e":0.01908880494970146,"cost":7.953668729042276,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"GCP","accountName":"gcp account 0","serviceName":"computeEngine","kilowattHours":53.70289639150512,"co2e":0.02577739026792246,"cost":10.740579278301025,"region":"us-east1","usesAverageCPUConstant":false},{"cloudProvider":"AZURE","accountName":"azure account 4","serviceName":"virtualMachines","kilowattHours":50.82389577797691,"co2e":0.011435376550044804,"cost":4.764740229185335,"region":"UK South","usesAverageCPUConstant":false},{"cloudProvider":"AliCloud","accountName":"ali account 1","serviceName":"virtualMachines","kilowattHours":72.05357763407658,"co2e":0.015524032067235646,"cost":2.874481284318876,"region":"cn-hangzhou","usesAverageCPUConstant":false}]},{"timestamp":"2023-01-01T05:00:00.000Z","serviceEstimates":[{"cloudProvider":"AWS","accountName":"aws account 0","serviceName":"ebs","kilowattHours":0.33278898489743725,"co2e":0.00012614998771608664,"cost":0.052562494881702775,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 1","serviceName":"s3","kilowattHours":0.00098701927251199,"co2e":4.052780094476032e-7,"cost":0.00016886583726983468,"region":"us-east-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 4","serviceName":"ec2","kilowattHours":50.904310297531644,"co2e":0.016399688935624875,"cost":6.833203723177032,"region":"us-west-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"rds","kilowattHours":50.12852654254913,"co2e":0.016149757010633425,"cost":6.729065421097261,"region":"us-west-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 2","serviceName":"lambda","kilowattHours":59.54349975897656,"co2e":0.022571094910135484,"cost":9.404622879223119,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"GCP","accountName":"gcp account 3","serviceName":"computeEngine","kilowattHours":63.69530490695243,"co2e":0.03057374635533717,"cost":12.739060981390487,"region":"us-east1","usesAverageCPUConstant":false},{"cloudProvider":"AZURE","accountName":"azure account 0","serviceName":"virtualMachines","kilowattHours":65.60925208439582,"co2e":0.014762081718989059,"cost":6.150867382912108,"region":"UK South","usesAverageCPUConstant":false},{"cloudProvider":"AliCloud","accountName":"ali account 1","serviceName":"virtualMachines","kilowattHours":71.05357763407658,"co2e":0.014524032067235647,"cost":2.474481284318876,"region":"cn-hangzhou","usesAverageCPUConstant":false}]},{"timestamp":"2022-12-01T05:00:00.000Z","serviceEstimates":[{"cloudProvider":"AWS","accountName":"aws account 4","serviceName":"ebs","kilowattHours":0.7421818551593697,"co2e":0.00028133813365340714,"cost":0.11722422235558633,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 1","serviceName":"s3","kilowattHours":0.0002180651158962681,"co2e":8.953928110793486e-8,"cost":0.00003730803379497286,"region":"us-east-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 0","serviceName":"ec2","kilowattHours":54.239336047864754,"co2e":0.017474124176532442,"cost":7.280885073555185,"region":"us-west-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 0","serviceName":"rds","kilowattHours":50.23120235816861,"co2e":0.016182835770124106,"cost":6.742848237551712,"region":"us-west-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"lambda","kilowattHours":50.86615400739505,"co2e":0.019281782133429234,"cost":8.034075888928848,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"GCP","accountName":"gcp account 3","serviceName":"computeEngine","kilowattHours":59.19683162717123,"co2e":0.02841447918104219,"cost":11.839366325434247,"region":"us-east1","usesAverageCPUConstant":false},{"cloudProvider":"AZURE","accountName":"azure account 2","serviceName":"virtualMachines","kilowattHours":50.27713613081028,"co2e":0.011312355629432311,"cost":4.7134815122634635,"region":"UK South","usesAverageCPUConstant":false},{"cloudProvider":"AliCloud","accountName":"ali account 1","serviceName":"virtualMachines","kilowattHours":76.05357763407658,"co2e":0.016524032067235645,"cost":2.974481284318876,"region":"cn-hangzhou","usesAverageCPUConstant":false}]},{"timestamp":"2022-11-01T04:00:00.000Z","serviceEstimates":[{"cloudProvider":"AWS","accountName":"aws account 0","serviceName":"ebs","kilowattHours":0.5648293345133781,"co2e":0.00021410929100465172,"cost":0.08921220458527156,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 4","serviceName":"s3","kilowattHours":0.00005263625919629122,"co2e":2.161286911607075e-8,"cost":0.000009005362131696147,"region":"us-east-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 0","serviceName":"ec2","kilowattHours":64.1169430041115,"co2e":0.02065636317680559,"cost":8.606817990335664,"region":"us-west-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 1","serviceName":"rds","kilowattHours":50.69439982629539,"co2e":0.016332062708838108,"cost":6.805026128682545,"region":"us-west-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 4","serviceName":"lambda","kilowattHours":50.69873776324742,"co2e":0.01921831982517644,"cost":8.007633260490184,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"GCP","accountName":"gcp account 2","serviceName":"computeEngine","kilowattHours":50.71620115791394,"co2e":0.024343776555798693,"cost":10.143240231582789,"region":"us-east1","usesAverageCPUConstant":false},{"cloudProvider":"AZURE","accountName":"azure account 0","serviceName":"virtualMachines","kilowattHours":50.146548832423804,"co2e":0.011282973487295355,"cost":4.701238953039732,"region":"UK South","usesAverageCPUConstant":false},{"cloudProvider":"AliCloud","accountName":"ali account 1","serviceName":"virtualMachines","kilowattHours":60.05357763407657,"co2e":0.013524032067235646,"cost":2.474481284318876,"region":"cn-hangzhou","usesAverageCPUConstant":false}]},{"timestamp":"2022-10-01T04:00:00.000Z","serviceEstimates":[{"cloudProvider":"AWS","accountName":"aws account 0","serviceName":"ebs","kilowattHours":0.35269028620932774,"co2e":0.00013369395410308364,"cost":0.05570581420961819,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 2","serviceName":"s3","kilowattHours":0.00007772936246220752,"co2e":3.1916298061882105e-8,"cost":0.000013298457525784212,"region":"us-east-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 4","serviceName":"ec2","kilowattHours":50.00593572462313,"co2e":0.016110262294594658,"cost":6.712609289414441,"region":"us-west-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"rds","kilowattHours":50.803461347266776,"co2e":0.016367198731864895,"cost":6.81966613827704,"region":"us-west-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 0","serviceName":"lambda","kilowattHours":52.62976125953012,"co2e":0.019950310970888823,"cost":8.312629571203678,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"GCP","accountName":"gcp account 4","serviceName":"computeEngine","kilowattHours":64.63052755166217,"co2e":0.03102265322479784,"cost":12.926105510332436,"region":"us-east1","usesAverageCPUConstant":false},{"cloudProvider":"AZURE","accountName":"azure account 1","serviceName":"virtualMachines","kilowattHours":53.31425378242701,"co2e":0.011995707101046077,"cost":4.998211292102533,"region":"UK South","usesAverageCPUConstant":false},{"cloudProvider":"AliCloud","accountName":"ali account 1","serviceName":"virtualMachines","kilowattHours":55.05357763407657,"co2e":0.012524032067235647,"cost":2.274481284318876,"region":"cn-hangzhou","usesAverageCPUConstant":false}]},{"timestamp":"2022-09-01T04:00:00.000Z","serviceEstimates":[{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"ebs","kilowattHours":0.3793961762889928,"co2e":0.00014381732914969223,"cost":0.059923887145705096,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 2","serviceName":"s3","kilowattHours":0.0007881261941471649,"co2e":3.2361092032637913e-7,"cost":0.00013483788346932464,"region":"us-east-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"ec2","kilowattHours":50.4099085739953,"co2e":0.016240409015558344,"cost":6.7668370898159775,"region":"us-west-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 1","serviceName":"rds","kilowattHours":56.61135475563019,"co2e":0.01823831032755711,"cost":7.599295969815463,"region":"us-west-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 4","serviceName":"lambda","kilowattHours":50.16319007556745,"co2e":0.019015310298755278,"cost":7.9230459578147,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"GCP","accountName":"gcp account 1","serviceName":"computeEngine","kilowattHours":50.2235650164992,"co2e":0.02410731120791962,"cost":10.044713003299842,"region":"us-east1","usesAverageCPUConstant":false},{"cloudProvider":"AZURE","accountName":"azure account 3","serviceName":"virtualMachines","kilowattHours":50.27485053251475,"co2e":0.011311841369815818,"cost":4.713267237423258,"region":"UK South","usesAverageCPUConstant":false},{"cloudProvider":"AliCloud","accountName":"ali account 1","serviceName":"virtualMachines","kilowattHours":58.05357763407657,"co2e":0.013524032067235646,"cost":2.474481284318876,"region":"cn-hangzhou","usesAverageCPUConstant":false}]},{"timestamp":"2022-08-01T04:00:00.000Z","serviceEstimates":[{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"ebs","kilowattHours":0.0025963667867079376,"co2e":9.842021614705911e-7,"cost":0.00041008423394607964,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"s3","kilowattHours":0.00038988295545011175,"co2e":1.600890605714595e-7,"cost":0.00006670377523810813,"region":"us-east-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 4","serviceName":"ec2","kilowattHours":50.58419719553395,"co2e":0.016296559057893588,"cost":6.790232940788996,"region":"us-west-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 1","serviceName":"rds","kilowattHours":50.09219672002729,"co2e":0.01613805274070103,"cost":6.724188641958764,"region":"us-west-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 0","serviceName":"lambda","kilowattHours":50.34788323768369,"co2e":0.01908532175102552,"cost":7.952217396260633,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"GCP","accountName":"gcp account 2","serviceName":"computeEngine","kilowattHours":50.876621782936894,"co2e":0.02442077845580971,"cost":10.17532435658738,"region":"us-east1","usesAverageCPUConstant":false},{"cloudProvider":"AZURE","accountName":"azure account 2","serviceName":"virtualMachines","kilowattHours":69.14280006058016,"co2e":0.015557130013630535,"cost":6.48213750567939,"region":"UK South","usesAverageCPUConstant":false},{"cloudProvider":"AliCloud","accountName":"ali account 1","serviceName":"virtualMachines","kilowattHours":65.05357763407658,"co2e":0.014524032067235647,"cost":2.664481284318876,"region":"cn-hangzhou","usesAverageCPUConstant":false}]},{"timestamp":"2022-07-01T04:00:00.000Z","serviceEstimates":[{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"ebs","kilowattHours":0.847821319053617,"co2e":0.00032138277959233556,"cost":0.1339094914968065,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"s3","kilowattHours":0.000630093974723783,"co2e":2.587216267733831e-7,"cost":0.00010780067782224297,"region":"us-east-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"ec2","kilowattHours":50.10307382046631,"co2e":0.016141556983518168,"cost":6.72564874313257,"region":"us-west-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 2","serviceName":"rds","kilowattHours":50.22293041559674,"co2e":0.016180170823201556,"cost":6.741737843000649,"region":"us-west-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"lambda","kilowattHours":68.18586584927158,"co2e":0.025847147981617528,"cost":10.769644992340638,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"GCP","accountName":"gcp account 4","serviceName":"computeEngine","kilowattHours":68.47759811709409,"co2e":0.032869247096205166,"cost":13.69551962341882,"region":"us-east1","usesAverageCPUConstant":false},{"cloudProvider":"AZURE","accountName":"azure account 1","serviceName":"virtualMachines","kilowattHours":50.5125618456214,"co2e":0.011365326415264814,"cost":4.735552673027006,"region":"UK South","usesAverageCPUConstant":false},{"cloudProvider":"AliCloud","accountName":"ali account 1","serviceName":"virtualMachines","kilowattHours":58.05357763407657,"co2e":0.013224032067235646,"cost":2.554481284318876,"region":"cn-hangzhou","usesAverageCPUConstant":false}]},{"timestamp":"2022-06-01T04:00:00.000Z","serviceEstimates":[{"cloudProvider":"AWS","accountName":"aws account 1","serviceName":"ebs","kilowattHours":0.9119831149849784,"co2e":0.00034570452741424076,"cost":0.144043553089267,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 1","serviceName":"s3","kilowattHours":0.00010168764085273696,"co2e":4.175375883526062e-8,"cost":0.000017397399514691924,"region":"us-east-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 2","serviceName":"ec2","kilowattHours":50.13033413155082,"co2e":0.01615033935615933,"cost":6.729308065066388,"region":"us-west-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 3","serviceName":"rds","kilowattHours":53.81264913939785,"co2e":0.017336659735292387,"cost":7.223608223038495,"region":"us-west-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 4","serviceName":"lambda","kilowattHours":63.45075892152083,"co2e":0.02405221573362198,"cost":10.021756555675827,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"GCP","accountName":"gcp account 4","serviceName":"computeEngine","kilowattHours":50.77403726191187,"co2e":0.0243715378857177,"cost":10.154807452382375,"region":"us-east1","usesAverageCPUConstant":false},{"cloudProvider":"AZURE","accountName":"azure account 4","serviceName":"virtualMachines","kilowattHours":50.373127573933836,"co2e":0.011333953704135112,"cost":4.722480710056297,"region":"UK South","usesAverageCPUConstant":false},{"cloudProvider":"AliCloud","accountName":"ali account 1","serviceName":"virtualMachines","kilowattHours":72.05357763407658,"co2e":0.015524032067235646,"cost":2.874481284318876,"region":"cn-hangzhou","usesAverageCPUConstant":false}]},{"timestamp":"2022-05-01T04:00:00.000Z","serviceEstimates":[{"cloudProvider":"AWS","accountName":"aws account 1","serviceName":"ebs","kilowattHours":0.8148394263939371,"co2e":0.00030888036652372336,"cost":0.12870015271821808,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 1","serviceName":"s3","kilowattHours":0.00010100550904187954,"co2e":4.147367005666808e-8,"cost":0.000017280695856945034,"region":"us-east-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 1","serviceName":"ec2","kilowattHours":50.933103987612505,"co2e":0.016408965312377156,"cost":6.837068880157149,"region":"us-west-1","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 2","serviceName":"rds","kilowattHours":50.92736661108625,"co2e":0.016407116918993825,"cost":6.836298716247428,"region":"us-west-2","usesAverageCPUConstant":false},{"cloudProvider":"AWS","accountName":"aws account 2","serviceName":"lambda","kilowattHours":70.47173348463286,"co2e":0.026713649540286294,"cost":11.130687308452623,"region":"us-east-1","usesAverageCPUConstant":false},{"cloudProvider":"GCP","accountName":"gcp account 3","serviceName":"computeEngine","kilowattHours":57.666701915218034,"co2e":0.02768001691930466,"cost":11.533340383043608,"region":"us-east1","usesAverageCPUConstant":false},{"cloudProvider":"AZURE","accountName":"azure account 2","serviceName":"virtualMachines","kilowattHours":50.773157853289746,"co2e":0.011423960516990192,"cost":4.759983548745914,"region":"UK South","usesAverageCPUConstant":false},{"cloudProvider":"AliCloud","accountName":"ali account 1","serviceName":"virtualMachines","kilowattHours":55.05357763407657,"co2e":0.013524032067235646,"cost":2.434481284318876,"region":"cn-hangzhou","usesAverageCPUConstant":false}]}],"recommendations":[{"cloudProvider":"AWS","accountId":"aws account 0","accountName":"aws account 0","region":"us-west-1","recommendationType":"Modify","instanceName":"example-instance-5","recommendationDetail":"Modify instance: example-instance-5.","resourceId":"i-0f12345678912b12I","kilowattHourSavings":116.513,"costSavings":15.640268196250002,"co2eSavings":0.037536643671},{"cloudProvider":"AWS","accountId":"aws account 1","accountName":"aws account 1","region":"us-east-2","recommendationType":"Modify","instanceName":"example-instance","recommendationDetail":"Modify instance: example-instance.","resourceId":"i-0f12345678912b12I","kilowattHourSavings":114.978,"costSavings":19.67120276,"co2eSavings":0.047210886624},{"cloudProvider":"AWS","accountId":"aws account 2","accountName":"aws account 2","region":"us-east-1","recommendationType":"Modify","instanceName":"example-instance-2","recommendationDetail":"Modify instance: example-instance-2.","resourceId":"i-0f12345678912b12I","kilowattHourSavings":18.419,"costSavings":2.909196629583334,"co2eSavings":0.0069820719110000005},{"cloudProvider":"AWS","accountId":"aws account 3","accountName":"aws account 3","region":"us-west-2","recommendationType":"Delete","instanceName":"example-instance","recommendationDetail":"Delete instance: example-instance.","resourceId":"i-0f12345678912b12I","kilowattHourSavings":11.195,"costSavings":1.50277481875,"co2eSavings":0.003606659565},{"cloudProvider":"AWS","accountId":"aws account 4","accountName":"aws account 4","region":"us-west-1","recommendationType":"Delete","instanceName":"example-instance","recommendationDetail":"Delete instance: example-instance.","resourceId":"i-0f12345678912b12I","kilowattHourSavings":111.717,"costSavings":14.996471141250002,"co2eSavings":0.035991530739},{"cloudProvider":"GCP","accountId":"gcp account 0","accountName":"gcp account 0","region":"us-west1","recommendationType":"DELETE_IMAGE","instanceName":"test-instance-1","recommendationDetail":"Save cost by performing a DELETE_IMAGE for instance: test-instance-1.","resourceId":6906976106869124000,"kilowattHourSavings":12.081,"costSavings":0.3926325,"co2eSavings":0.0009423179999999999},{"cloudProvider":"GCP","accountId":"gcp account 1","accountName":"gcp account 1","region":"us-west1","recommendationType":"SNAPSHOT_AND_DELETE_DISK","instanceName":"test-instance-3","recommendationDetail":"Save cost by performing a SNAPSHOT_AND_DELETE_DISK for instance: test-instance-3.","resourceId":4256745502855943000,"kilowattHourSavings":18.742,"costSavings":0.6091150000000001,"co2eSavings":0.001461876},{"cloudProvider":"GCP","accountId":"gcp account 2","accountName":"gcp account 2","region":"us-west1","recommendationType":"CHANGE_MACHINE_TYPE","instanceName":"test-instance-10","recommendationDetail":"Save cost by performing a CHANGE_MACHINE_TYPE for instance: test-instance-10.","resourceId":9625521363699055000,"kilowattHourSavings":16.989,"costSavings":0.5521425000000001,"co2eSavings":0.001325142},{"cloudProvider":"GCP","accountId":"gcp account 3","accountName":"gcp account 3","region":"us-east1","recommendationType":"DELETE_ADDRESS","instanceName":"test-instance-8","recommendationDetail":"Save cost by performing a DELETE_ADDRESS for instance: test-instance-8.","resourceId":9351508929180877000,"kilowattHourSavings":19.946,"costSavings":3.9892000000000007,"co2eSavings":0.00957408},{"cloudProvider":"GCP","accountId":"gcp account 4","accountName":"gcp account 4","region":"us-west2","recommendationType":"DELETE_DISK","instanceName":"test-instance-14","recommendationDetail":"Save cost by performing a DELETE_DISK for instance: test-instance-14.","resourceId":7840914330904416000,"kilowattHourSavings":18.782,"costSavings":1.9799358333333337,"co2eSavings":0.0047518460000000005},{"cloudProvider":"GCP","accountId":"gcp account 2","accountName":"gcp account 2","region":"us-east1","recommendationType":"STOP_VM","instanceName":"test-instance-9","recommendationDetail":"Save cost by performing a STOP_VM for instance: test-instance-9.","resourceId":8928403120086348000,"kilowattHourSavings":116.483,"costSavings":23.296600000000005,"co2eSavings":0.055911840000000004},{"cloudProvider":"GCP","accountId":"gcp account 4","accountName":"gcp account 4","region":"us-east1","recommendationType":"STOP_VM","instanceName":"test-instance-9","recommendationDetail":"Save cost by performing a STOP_VM for instance: test-instance-9.","resourceId":8928403120086348000,"kilowattHourSavings":0.0001,"costSavings":0.000020000000000000005,"co2eSavings":4.8000000000000006e-8},{"cloudProvider":"GCP","accountId":"gcp account 4","accountName":"gcp account 4","region":"us-east1","recommendationType":"STOP_VM","instanceName":"test-instance-9","recommendationDetail":"Save cost by performing a STOP_VM for instance: test-instance-9.","resourceId":8928403120086348000,"kilowattHourSavings":0,"costSavings":0,"co2eSavings":0},{"cloudProvider":"GCP","accountId":"gcp account 4","accountName":"gcp account 4","region":"us-east1","recommendationType":"STOP_VM","instanceName":"test-instance-9","recommendationDetail":"Save cost by performing a STOP_VM for instance: test-instance-9.","resourceId":8928403120086348000,"kilowattHourSavings":0.001,"costSavings":0.00020000000000000004,"co2eSavings":4.800000000000001e-7},{"cloudProvider":"GCP","accountId":"gcp account 4","accountName":"gcp account 4","region":"us-east1","recommendationType":"STOP_VM","instanceName":"test-instance-9","recommendationDetail":"Save cost by performing a STOP_VM for instance: test-instance-9.","resourceId":8928403120086348000,"kilowattHourSavings":0.002,"costSavings":0.0004000000000000001,"co2eSavings":9.600000000000001e-7}]} \ No newline at end of file diff --git a/scripts/update-mock-data.js b/scripts/update-mock-data.js index 7f786e436..512cdfec5 100644 --- a/scripts/update-mock-data.js +++ b/scripts/update-mock-data.js @@ -27,16 +27,22 @@ async function update() { ) const { mtPerKwHour } = regionObj const updatedC02e = serviceEstimate.kilowattHours * mtPerKwHour + const updatedCost = serviceEstimate.co2e / 0.0024 // Sample cost:co2e ratio serviceEstimate.co2e = updatedC02e + serviceEstimate.cost = updatedCost } }) }) mockData.recommendations.forEach((recommendation) => { - const regionObj = mockData.emissions.find(o => o.region === recommendation.region) + const regionObj = mockData.emissions.find( + (o) => o.region === recommendation.region, + ) const { mtPerKwHour } = regionObj const updatedC02e = recommendation.kilowattHourSavings * mtPerKwHour + const updatingCostSavings = recommendation.co2eSavings / 0.0024 // Sample cost:co2e ratio recommendation.co2eSavings = updatedC02e + recommendation.costSavings = updatingCostSavings }) fs.writeFileSync( From c9799b25f54c4c7fd40c1765fbe196d7b9765eae Mon Sep 17 00:00:00 2001 From: Arik Smith Date: Tue, 18 Jul 2023 23:25:22 -0400 Subject: [PATCH 192/251] adds multiplier to mock data costs --- scripts/update-mock-data.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/update-mock-data.js b/scripts/update-mock-data.js index 512cdfec5..19a62f523 100644 --- a/scripts/update-mock-data.js +++ b/scripts/update-mock-data.js @@ -27,7 +27,7 @@ async function update() { ) const { mtPerKwHour } = regionObj const updatedC02e = serviceEstimate.kilowattHours * mtPerKwHour - const updatedCost = serviceEstimate.co2e / 0.0024 // Sample cost:co2e ratio + const updatedCost = (updatedC02e / 0.0024) * 100 // Sample cost:co2e ratio serviceEstimate.co2e = updatedC02e serviceEstimate.cost = updatedCost } @@ -40,7 +40,7 @@ async function update() { ) const { mtPerKwHour } = regionObj const updatedC02e = recommendation.kilowattHourSavings * mtPerKwHour - const updatingCostSavings = recommendation.co2eSavings / 0.0024 // Sample cost:co2e ratio + const updatingCostSavings = updatedC02e / 0.0024 // Sample cost:co2e ratio recommendation.co2eSavings = updatedC02e recommendation.costSavings = updatingCostSavings }) From 15915d6c0aaec9284964eadf30067a7de1f5bd4a Mon Sep 17 00:00:00 2001 From: Arik Smith Date: Thu, 20 Jul 2023 10:04:14 -0400 Subject: [PATCH 193/251] increase magnitude of c02e for demo app --- scripts/update-mock-data.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/update-mock-data.js b/scripts/update-mock-data.js index 19a62f523..85eb1a387 100644 --- a/scripts/update-mock-data.js +++ b/scripts/update-mock-data.js @@ -26,7 +26,7 @@ async function update() { (o) => o.region === serviceEstimate.region, ) const { mtPerKwHour } = regionObj - const updatedC02e = serviceEstimate.kilowattHours * mtPerKwHour + const updatedC02e = serviceEstimate.kilowattHours * mtPerKwHour * 10 const updatedCost = (updatedC02e / 0.0024) * 100 // Sample cost:co2e ratio serviceEstimate.co2e = updatedC02e serviceEstimate.cost = updatedCost From fb4d730da3d9a95ef42b7427953ce4acbdaa203f Mon Sep 17 00:00:00 2001 From: Arik Smith Date: Thu, 20 Jul 2023 14:57:44 -0400 Subject: [PATCH 194/251] randomizes magnitude for mock data costs --- scripts/update-mock-data.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/scripts/update-mock-data.js b/scripts/update-mock-data.js index 85eb1a387..5866df309 100644 --- a/scripts/update-mock-data.js +++ b/scripts/update-mock-data.js @@ -27,7 +27,8 @@ async function update() { ) const { mtPerKwHour } = regionObj const updatedC02e = serviceEstimate.kilowattHours * mtPerKwHour * 10 - const updatedCost = (updatedC02e / 0.0024) * 100 // Sample cost:co2e ratio + let updatedCost = (updatedC02e / 0.0024) // Sample cost:co2e ratio + updatedCost *= Math.floor(Math.random() * (10 - 2 + 1) + 2) // Increase magnitude serviceEstimate.co2e = updatedC02e serviceEstimate.cost = updatedCost } @@ -40,7 +41,8 @@ async function update() { ) const { mtPerKwHour } = regionObj const updatedC02e = recommendation.kilowattHourSavings * mtPerKwHour - const updatingCostSavings = updatedC02e / 0.0024 // Sample cost:co2e ratio + let updatingCostSavings = updatedC02e / 0.0024 // Sample cost:co2e ratio + updatedCostSavings *= Math.floor(Math.random() * (10 - 2 + 1) + 2) // Increase magnitude recommendation.co2eSavings = updatedC02e recommendation.costSavings = updatingCostSavings }) From d3616ff9232bd8eff8e5401221d7a35d249ce097 Mon Sep 17 00:00:00 2001 From: Arik Smith Date: Thu, 20 Jul 2023 16:18:12 -0400 Subject: [PATCH 195/251] change unit of request splitting frequency based on groupBy parameter --- .../cli/src/SeedCacheFile/seedCacheFile.ts | 18 ++- .../SeedCacheFile/seedCacheFile.test.ts | 113 +++++++++++++----- 2 files changed, 96 insertions(+), 35 deletions(-) diff --git a/packages/cli/src/SeedCacheFile/seedCacheFile.ts b/packages/cli/src/SeedCacheFile/seedCacheFile.ts index 2249089c8..b3109ff79 100644 --- a/packages/cli/src/SeedCacheFile/seedCacheFile.ts +++ b/packages/cli/src/SeedCacheFile/seedCacheFile.ts @@ -46,12 +46,13 @@ export default async function seedCacheFile(): Promise { const endDate = moment.utc(end) // Grab the default length of the request window based on the provided date range - let daysPerRequest = endDate.diff(currentDate, 'days') + 1 + let timePerRequest = + endDate.diff(currentDate, `${groupBy}s` as moment.unitOfTime.Diff) + 1 if (fetchMethod === 'split') { - daysPerRequest = + timePerRequest = parseInt( await inputPrompt( - 'How many days would you like to fetch per request? [1]', + `How many ${groupBy}s would you like to fetch per request? [1]`, ), ) || 1 } @@ -82,10 +83,12 @@ export default async function seedCacheFile(): Promise { await MongoDbCacheManager.createDbConnection() } - // Makes getCostAndEstimates requests in chunks based on request method and day frequency + // Makes getCostAndEstimates requests in chunks based on request method and time frequency while (currentDate.isSameOrBefore(endDate)) { // Use the current date window as the inclusive start/end date of the request - let nextDate = currentDate.clone().add(daysPerRequest - 1, 'day') + let nextDate = currentDate + .clone() + .add(timePerRequest - 1, groupBy as moment.unitOfTime.DurationConstructor) // Enforce end date as boundary if request window passes it if (nextDate.isAfter(endDate)) nextDate = endDate.clone() @@ -98,7 +101,10 @@ export default async function seedCacheFile(): Promise { await app.getCostAndEstimates(request) // Slide to the beginning of the next date window - currentDate.add(daysPerRequest, 'day') + currentDate.add( + timePerRequest, + groupBy as moment.unitOfTime.DurationConstructor, + ) } if (configLoader().CACHE_MODE === 'MONGODB') { diff --git a/packages/cli/src/__tests__/SeedCacheFile/seedCacheFile.test.ts b/packages/cli/src/__tests__/SeedCacheFile/seedCacheFile.test.ts index c64dc146a..cf1161640 100644 --- a/packages/cli/src/__tests__/SeedCacheFile/seedCacheFile.test.ts +++ b/packages/cli/src/__tests__/SeedCacheFile/seedCacheFile.test.ts @@ -7,9 +7,10 @@ import { EstimationRequestValidationError, EstimationResult, } from '@cloud-carbon-footprint/common' +import { MongoDbCacheManager } from '@cloud-carbon-footprint/app' +import moment from 'moment' import seedCacheFile from '../../SeedCacheFile/seedCacheFile' import * as common from '../../common' -import { MongoDbCacheManager } from '@cloud-carbon-footprint/app' const mockGetCostAndEstimates = jest.fn() jest.mock('../../common') @@ -201,35 +202,89 @@ describe('seedCacheFile', () => { 'Done! Estimates have been successfully seeded to the cache file!', ) }) - it('makes separate requests for a specified per day frequency', async () => { - mockInputPrompts.mockResolvedValueOnce(2) // option given for daysPerRequest - mockInputPrompts.mockResolvedValueOnce('') // No option given for cloud provider to seed - - const expectedResponse: EstimationResult[] = [] - mockGetCostAndEstimates.mockRestore() - mockGetCostAndEstimates.mockResolvedValue(expectedResponse) - await seedCacheFile() - - expect(mockGetCostAndEstimates).toHaveBeenCalledTimes(2) - - expect(console.info).toHaveBeenNthCalledWith( - 1, - 'Seeding cache file using split requests...', - ) - expect(console.info).toHaveBeenNthCalledWith( - 2, - 'Fetching estimates from 2020-07-01 to 2020-07-02...', - ) - expect(console.info).toHaveBeenNthCalledWith( - 3, - 'Fetching estimates for 2020-07-03...', - ) - - expect(console.info).lastCalledWith( - 'Done! Estimates have been successfully seeded to the cache file!', - ) - }) + // it('makes separate requests for a specified per day frequency', async () => { + // mockInputPrompts.mockResolvedValueOnce(2) // option given for daysPerRequest + // mockInputPrompts.mockResolvedValueOnce('') // No option given for cloud provider to seed + // + // const expectedResponse: EstimationResult[] = [] + // mockGetCostAndEstimates.mockRestore() + // mockGetCostAndEstimates.mockResolvedValue(expectedResponse) + // + // await seedCacheFile() + // + // expect(mockGetCostAndEstimates).toHaveBeenCalledTimes(2) + // + // expect(console.info).toHaveBeenNthCalledWith( + // 1, + // 'Seeding cache file using split requests...', + // ) + // expect(console.info).toHaveBeenNthCalledWith( + // 2, + // 'Fetching estimates from 2020-07-01 to 2020-07-02...', + // ) + // expect(console.info).toHaveBeenNthCalledWith( + // 3, + // 'Fetching estimates for 2020-07-03...', + // ) + // + // expect(console.info).lastCalledWith( + // 'Done! Estimates have been successfully seeded to the cache file!', + // ) + // }) + + it.each([ + ['day', '2020-07-02'], + ['week', '2020-07-08'], + ['month', '2020-08-01'], + ['quarter', '2020-10-01'], + ['year', '2021-07-01'], + ])( + 'makes separate requests for a specified per %s frequency', + async (groupBy: string, nextDate: string) => { + // Required for setup between each parameterized test + mockInputPrompts.mockRestore() + mockListPrompts.mockRestore() + mockInputPrompts.mockResolvedValueOnce('2020-07-01') + + // create a variable that adds 1 unit of the groupBy parameter to the nextDate + const endDate = moment + .utc('2020-07-01') + .add(2, groupBy as moment.unitOfTime.DurationConstructor) + .format('YYYY-MM-DD') + mockInputPrompts.mockResolvedValueOnce(endDate) // option given for end date + + mockListPrompts + .mockResolvedValueOnce(groupBy) + .mockResolvedValueOnce('split') + + mockInputPrompts.mockResolvedValueOnce(2) // option given for request frequency + mockInputPrompts.mockResolvedValueOnce('') // No option given for cloud provider to seed + + mockListPrompts.mockResolvedValueOnce(groupBy) + + const expectedResponse: EstimationResult[] = [] + mockGetCostAndEstimates.mockRestore() + mockGetCostAndEstimates.mockResolvedValue(expectedResponse) + + await seedCacheFile() + + expect(mockGetCostAndEstimates).toHaveBeenCalledTimes(2) + + expect(console.info).toHaveBeenNthCalledWith( + 1, + 'Seeding cache file using split requests...', + ) + expect(console.info).toHaveBeenNthCalledWith( + 2, + `Fetching estimates from 2020-07-01 to ${nextDate}...`, + ) + + expect(console.info).lastCalledWith( + 'Done! Estimates have been successfully seeded to the cache file!', + ) + }, + ) }) }) From f1f3fab1b30e63fe631d5204e73aa91e24f6d842 Mon Sep 17 00:00:00 2001 From: Arik Smith Date: Thu, 20 Jul 2023 16:20:49 -0400 Subject: [PATCH 196/251] update create-app cli templates --- .../cli/src/SeedCacheFile/seedCacheFile.ts | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/packages/create-app/templates/default-app/packages/cli/src/SeedCacheFile/seedCacheFile.ts b/packages/create-app/templates/default-app/packages/cli/src/SeedCacheFile/seedCacheFile.ts index 2249089c8..b3109ff79 100644 --- a/packages/create-app/templates/default-app/packages/cli/src/SeedCacheFile/seedCacheFile.ts +++ b/packages/create-app/templates/default-app/packages/cli/src/SeedCacheFile/seedCacheFile.ts @@ -46,12 +46,13 @@ export default async function seedCacheFile(): Promise { const endDate = moment.utc(end) // Grab the default length of the request window based on the provided date range - let daysPerRequest = endDate.diff(currentDate, 'days') + 1 + let timePerRequest = + endDate.diff(currentDate, `${groupBy}s` as moment.unitOfTime.Diff) + 1 if (fetchMethod === 'split') { - daysPerRequest = + timePerRequest = parseInt( await inputPrompt( - 'How many days would you like to fetch per request? [1]', + `How many ${groupBy}s would you like to fetch per request? [1]`, ), ) || 1 } @@ -82,10 +83,12 @@ export default async function seedCacheFile(): Promise { await MongoDbCacheManager.createDbConnection() } - // Makes getCostAndEstimates requests in chunks based on request method and day frequency + // Makes getCostAndEstimates requests in chunks based on request method and time frequency while (currentDate.isSameOrBefore(endDate)) { // Use the current date window as the inclusive start/end date of the request - let nextDate = currentDate.clone().add(daysPerRequest - 1, 'day') + let nextDate = currentDate + .clone() + .add(timePerRequest - 1, groupBy as moment.unitOfTime.DurationConstructor) // Enforce end date as boundary if request window passes it if (nextDate.isAfter(endDate)) nextDate = endDate.clone() @@ -98,7 +101,10 @@ export default async function seedCacheFile(): Promise { await app.getCostAndEstimates(request) // Slide to the beginning of the next date window - currentDate.add(daysPerRequest, 'day') + currentDate.add( + timePerRequest, + groupBy as moment.unitOfTime.DurationConstructor, + ) } if (configLoader().CACHE_MODE === 'MONGODB') { From 056c0c8b0ab0910ed7a613fc6814fd5ae6163ed3 Mon Sep 17 00:00:00 2001 From: Arik Smith Date: Thu, 20 Jul 2023 16:22:17 -0400 Subject: [PATCH 197/251] changeset: Changes seed-cache-file request splitting frequency to be based on groupBy unit --- .changeset/thick-tomatoes-hug.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/thick-tomatoes-hug.md diff --git a/.changeset/thick-tomatoes-hug.md b/.changeset/thick-tomatoes-hug.md new file mode 100644 index 000000000..6aa132faa --- /dev/null +++ b/.changeset/thick-tomatoes-hug.md @@ -0,0 +1,5 @@ +--- +'@cloud-carbon-footprint/cli': patch +--- + +Changes seed-cache-file request splitting frequency to be based on groupBy parameter From fa933d453429f30e20f8d39b6f1b8a5f6c52563c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 11 Jul 2023 10:25:23 +0000 Subject: [PATCH 198/251] Bump semver from 5.7.1 to 5.7.2 in /microsite Bumps [semver](https://github.com/npm/node-semver) from 5.7.1 to 5.7.2. - [Release notes](https://github.com/npm/node-semver/releases) - [Changelog](https://github.com/npm/node-semver/blob/v5.7.2/CHANGELOG.md) - [Commits](https://github.com/npm/node-semver/compare/v5.7.1...v5.7.2) --- updated-dependencies: - dependency-name: semver dependency-type: indirect ... Signed-off-by: dependabot[bot] --- microsite/yarn.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/microsite/yarn.lock b/microsite/yarn.lock index 93682a31c..1442b0873 100644 --- a/microsite/yarn.lock +++ b/microsite/yarn.lock @@ -9539,11 +9539,11 @@ __metadata: linkType: hard "semver@npm:^5.4.1": - version: 5.7.1 - resolution: "semver@npm:5.7.1" + version: 5.7.2 + resolution: "semver@npm:5.7.2" bin: - semver: ./bin/semver - checksum: 57fd0acfd0bac382ee87cd52cd0aaa5af086a7dc8d60379dfe65fea491fb2489b6016400813930ecd61fd0952dae75c115287a1b16c234b1550887117744dfaf + semver: bin/semver + checksum: fb4ab5e0dd1c22ce0c937ea390b4a822147a9c53dbd2a9a0132f12fe382902beef4fbf12cf51bb955248d8d15874ce8cd89532569756384f994309825f10b686 languageName: node linkType: hard From 53a1afb46bf57c50cd63cb4a1126e840eac55172 Mon Sep 17 00:00:00 2001 From: Arik Smith <--global> Date: Wed, 2 Aug 2023 16:57:08 -0400 Subject: [PATCH 199/251] fixes recommendations tests and disables forecast validation for integration env --- packages/client/src/Config.ts | 2 +- .../integration-tests/tests/page-model.js | 61 ++++++++++--------- .../tests/recommendations.test.js | 41 ++++++------- 3 files changed, 51 insertions(+), 53 deletions(-) diff --git a/packages/client/src/Config.ts b/packages/client/src/Config.ts index 4f9c90305..4ea4e444e 100644 --- a/packages/client/src/Config.ts +++ b/packages/client/src/Config.ts @@ -42,7 +42,7 @@ if (process.env.REACT_APP_TEST_MODE === 'true') { baseUrl = 'http://127.0.0.1:3000/api' endDate = null minDateAge = '0' - disableForecastValidation = false + disableForecastValidation = true } const appConfig: ClientConfig = { diff --git a/packages/integration-tests/tests/page-model.js b/packages/integration-tests/tests/page-model.js index 392137b38..fe93fea0c 100644 --- a/packages/integration-tests/tests/page-model.js +++ b/packages/integration-tests/tests/page-model.js @@ -30,18 +30,6 @@ class Page { //co2 amount dropdowns this.totalCo2Amount = Selector('#metric-one') - this.cloudProviderDropDown = Selector('#cloud-provider-filter') - .sibling('div') - .child('button') - this.accountsDropDown = Selector('#accounts-filter') - .sibling('div') - .child('button') - this.servicesDropDown = Selector('#services-filter') - .sibling('div') - .child('button') - this.awsDropdownItem = Selector('#cloud-provider-filter-option-1') - this.accountsDropdownItem = Selector('#accounts-filter-option-1') - this.servicesDropdownItem = Selector('#services-filter-option-1') //emissions breakdown this.flightsButton = Selector('#flights') @@ -74,22 +62,39 @@ class Page { ) this.recAccounts = Selector('span').withText('Accounts') - //forecast card components (subcomponents not included due to conditional rendering) - // this.lastThirtyDayTotal = Selector( - // "[data-testid='forecast-card-last-thirty-day-total']", - // ) - // this.projectedThirtyDayTotal = Selector( - // "[data-testid='forecast-card-projected-thirty-day-total']", - // ) - // this.forecastEquivalencyCard = Selector( - // "[data-testid='forecast-equivalency-card']", - // ) - // this.treeSeedlingsGrown = Selector("[data-testid='tree-seedlings-grown']") - // this.costSavingsPerMonth = Selector( - // "[data-testid='cost-savings-per-month']", - // ) - this.forecastErrorMessage = Selector( - "[data-testid='forecast-error-message']", + // forecast card components + this.lastThirtyDayTotal = Selector( + "[data-testid='forecast-card-last-thirty-day-total']", + ) + this.projectedThirtyDayTotal = Selector( + "[data-testid='forecast-card-projected-thirty-day-total']", + ) + this.forecastEquivalencyCard = Selector( + "[data-testid='forecast-equivalency-card']", + ) + this.treeSeedlingsGrown = Selector("[data-testid='tree-seedlings-grown']") + this.costSavingsPerMonth = Selector( + "[data-testid='cost-savings-per-month']", + ) + + //units of measure + this.unitOfMeasureLastThirtyDayTotal = Selector( + "[data-testid='unit-of-measure-last-thirty-day-total']", + ) + this.unitOfMeasureProjectedThirtyDayTotal = Selector( + "[data-testid='unit-of-measure-projected-thirty-day-total']", + ) + this.co2eSavingsLastThirtyDayTotal = Selector( + "[data-testid='co2e-savings-last-thirty-day-total'", + ) + this.co2eSavingsProjectedThirtyDayTotal = Selector( + "[data-testid='co2e-savings-projected-thirty-day-total'", + ) + this.costSavingsLastThirtyDayTotal = Selector( + "[data-testid='cost-savings-last-thirty-day-total'", + ) + this.costSavingsProjectedThirtyDayTotal = Selector( + "[data-testid='cost-savings-projected-thirty-day-total'", ) //table components diff --git a/packages/integration-tests/tests/recommendations.test.js b/packages/integration-tests/tests/recommendations.test.js index 358598b53..def500121 100644 --- a/packages/integration-tests/tests/recommendations.test.js +++ b/packages/integration-tests/tests/recommendations.test.js @@ -1,7 +1,7 @@ /* * © 2021 Thoughtworks, Inc. */ -import { Selector, ClientFunction } from 'testcafe' +import { ClientFunction } from 'testcafe' import waitOn from 'wait-on' import page from './page-model' const getLocation = ClientFunction(() => document.location.href) @@ -32,14 +32,11 @@ test('filter components render with correct data when app loads', async (t) => { }) test('card components render with data when app loads', async (t) => { - // commented out because the error message is not always present - // await t.expect(page.errorMessage.exists).ok() - await t.expect(page.forecastErrorMessage.exists).ok() - // await t.expect(page.lastThirtyDayTotal.exists).ok() - // await t.expect(page.projectedThirtyDayTotal.exists).ok() - // await t.expect(page.forecastEquivalencyCard.exists).ok() - // await t.expect(page.treeSeedlingsGrown.exists).ok() - // await t.expect(page.costSavingsPerMonth.exists).ok() + await t.expect(page.lastThirtyDayTotal.exists).ok() + await t.expect(page.projectedThirtyDayTotal.exists).ok() + await t.expect(page.forecastEquivalencyCard.exists).ok() + await t.expect(page.treeSeedlingsGrown.exists).ok() + await t.expect(page.costSavingsPerMonth.exists).ok() }) test('table components renders with data when app loads', async (t) => { @@ -48,10 +45,6 @@ test('table components renders with data when app loads', async (t) => { }) test('toggle changes unit of measure', async (t) => { - //check projected totals - // commented out because the error message is not always present - // await t.expect(page.errorMessage.exists).ok() - //check first cell await t .expect(page.tableSavingsColumn.textContent) @@ -59,17 +52,17 @@ test('toggle changes unit of measure', async (t) => { await t.expect(page.firstSavingsCell.exists).ok() //click kilogram toggle await t.click(page.toggle, { isTrusted: true }) - //recheck data in - kg instead of metric tons, so 1000 - // await t - // .expect(page.unitOfMeasureLastThirtyDayTotal.textContent) - // .eql('Kilograms CO2e') - // await t - // .expect(page.unitOfMeasureProjectedThirtyDayTotal.textContent) - // .eql('Kilograms CO2e') - // await t.expect(page.co2eSavingsLastThirtyDayTotal.exists).ok() - // await t.expect(page.co2eSavingsProjectedThirtyDayTotal.exists).ok() - // await t.expect(page.costSavingsLastThirtyDayTotal.exists).ok() - // await t.expect(page.costSavingsProjectedThirtyDayTotal.exists).ok() + // recheck data in - kg instead of metric tons, so 1000 + await t + .expect(page.unitOfMeasureLastThirtyDayTotal.textContent) + .eql('Kilograms CO2e') + await t + .expect(page.unitOfMeasureProjectedThirtyDayTotal.textContent) + .eql('Kilograms CO2e') + await t.expect(page.co2eSavingsLastThirtyDayTotal.exists).ok() + await t.expect(page.co2eSavingsProjectedThirtyDayTotal.exists).ok() + await t.expect(page.costSavingsLastThirtyDayTotal.exists).ok() + await t.expect(page.costSavingsProjectedThirtyDayTotal.exists).ok() await t .expect(page.tableSavingsColumn.textContent) .eql('Potential Carbon Savings (kg)') From 8a8482c817f84bab583389f4b9f130265fc536d5 Mon Sep 17 00:00:00 2001 From: Arik Smith <--global> Date: Wed, 2 Aug 2023 17:08:41 -0400 Subject: [PATCH 200/251] changeset: Integration tests now disable forecast validation for testing, along with improved assertions --- .changeset/lucky-walls-deny.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/lucky-walls-deny.md diff --git a/.changeset/lucky-walls-deny.md b/.changeset/lucky-walls-deny.md new file mode 100644 index 000000000..b7960db76 --- /dev/null +++ b/.changeset/lucky-walls-deny.md @@ -0,0 +1,5 @@ +--- +'@cloud-carbon-footprint/integration-tests': minor +--- + +Integration tests now disable forecast validation for testing, along with improved assertions From 3ed2b840a42ca4c8465d803abf523e7183706eeb Mon Sep 17 00:00:00 2001 From: Arik Smith <--global> Date: Thu, 3 Aug 2023 10:55:14 -0400 Subject: [PATCH 201/251] updates testcafe version and reduces concurrency count --- packages/integration-tests/.testcaferc.json | 2 +- packages/integration-tests/package.json | 2 +- .../tests/recommendations.test.js | 2 +- yarn.lock | 576 ++++++++++-------- 4 files changed, 321 insertions(+), 261 deletions(-) diff --git a/packages/integration-tests/.testcaferc.json b/packages/integration-tests/.testcaferc.json index 7626238ef..d7b01b22e 100644 --- a/packages/integration-tests/.testcaferc.json +++ b/packages/integration-tests/.testcaferc.json @@ -3,7 +3,7 @@ { "module": "@testing-library/dom/dist/@testing-library/dom.umd.js" } ], "browsers": ["chrome:headless"], - "concurrency": 3, + "concurrency": 2, "selectorTimeout": 5000, "assertionTimeout": 5000, "appCommand": "yarn start", diff --git a/packages/integration-tests/package.json b/packages/integration-tests/package.json index 8e132e045..600005c7b 100644 --- a/packages/integration-tests/package.json +++ b/packages/integration-tests/package.json @@ -24,7 +24,7 @@ "eslint-plugin-testcafe": "^0.2.1", "lint-staged": "^12.1.7", "prettier": "^2.5.1", - "testcafe": "^1.18.1", + "testcafe": "^3.1.0", "wait-on": "^6.0.0" }, "lint-staged": { diff --git a/packages/integration-tests/tests/recommendations.test.js b/packages/integration-tests/tests/recommendations.test.js index def500121..900b4fce3 100644 --- a/packages/integration-tests/tests/recommendations.test.js +++ b/packages/integration-tests/tests/recommendations.test.js @@ -51,7 +51,7 @@ test('toggle changes unit of measure', async (t) => { .eql('Potential Carbon Savings (t)') await t.expect(page.firstSavingsCell.exists).ok() //click kilogram toggle - await t.click(page.toggle, { isTrusted: true }) + await t.click(page.toggle) // recheck data in - kg instead of metric tons, so 1000 await t .expect(page.unitOfMeasureLastThirtyDayTotal.textContent) diff --git a/yarn.lock b/yarn.lock index 333358659..ed5328c45 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2781,7 +2781,7 @@ __metadata: eslint-plugin-testcafe: ^0.2.1 lint-staged: ^12.1.7 prettier: ^2.5.1 - testcafe: ^1.18.1 + testcafe: ^3.1.0 wait-on: ^6.0.0 languageName: unknown linkType: soft @@ -2996,6 +2996,13 @@ __metadata: languageName: node linkType: hard +"@devexpress/bin-v8-flags-filter@npm:^1.3.0": + version: 1.3.0 + resolution: "@devexpress/bin-v8-flags-filter@npm:1.3.0" + checksum: 7787f9ca8d9d9ae2c782460db2b0044d4a800c4da19f141dca11993077fe10f212a9eb8d1f2c6236031189d1216b638f79664343190d9ed588b887beeb88b8ba + languageName: node + linkType: hard + "@devexpress/error-stack-parser@npm:^2.0.6": version: 2.0.6 resolution: "@devexpress/error-stack-parser@npm:2.0.6" @@ -3005,6 +3012,20 @@ __metadata: languageName: node linkType: hard +"@electron/asar@npm:^3.2.3": + version: 3.2.4 + resolution: "@electron/asar@npm:3.2.4" + dependencies: + chromium-pickle-js: ^0.2.0 + commander: ^5.0.0 + glob: ^7.1.6 + minimatch: ^3.0.4 + bin: + asar: bin/asar.js + checksum: 06e3e8fe7c894f7e7727410af5a9957ec77088f775b22441acf4ef718a9e6642a4dc1672f77ee1ce325fc367c8d59ac1e02f7db07869c8ced8a00132a3b54643 + languageName: node + linkType: hard + "@emotion/babel-plugin@npm:^11.7.1": version: 11.9.2 resolution: "@emotion/babel-plugin@npm:11.9.2" @@ -4805,13 +4826,6 @@ __metadata: languageName: node linkType: hard -"@miherlosev/esm@npm:3.2.26": - version: 3.2.26 - resolution: "@miherlosev/esm@npm:3.2.26" - checksum: 838745262ab4500ea8a1f808b090c1842bd4d76971d6a6ba6a12c91c0678ef897dfec4ea1272037366bb7f1ec31d8674119698e867da706f265beb6504ebc8f2 - languageName: node - linkType: hard - "@mui/base@npm:5.0.0-alpha.111": version: 5.0.0-alpha.111 resolution: "@mui/base@npm:5.0.0-alpha.111" @@ -7064,6 +7078,15 @@ __metadata: languageName: node linkType: hard +"acorn-hammerhead@npm:0.6.2": + version: 0.6.2 + resolution: "acorn-hammerhead@npm:0.6.2" + dependencies: + "@types/estree": 0.0.46 + checksum: de60dcdcb8787ba9f4ba68869b1204bc4a5429905050dd80c8ca022138d0f91ca0997984cb5aa2e7f158443f0f27481a91244ba35898f2e1a7f783f83c9729cd + languageName: node + linkType: hard + "acorn-import-assertions@npm:^1.7.6": version: 1.8.0 resolution: "acorn-import-assertions@npm:1.8.0" @@ -7725,7 +7748,7 @@ __metadata: languageName: node linkType: hard -"async@npm:^3.2.3": +"async@npm:^3.2.0, async@npm:^3.2.3": version: 3.2.4 resolution: "async@npm:3.2.4" checksum: 43d07459a4e1d09b84a20772414aa684ff4de085cbcaec6eea3c7a8f8150e8c62aa6cd4e699fe8ee93c3a5b324e777d34642531875a0817a35697522c1b02e89 @@ -7945,16 +7968,16 @@ __metadata: languageName: node linkType: hard -"babel-plugin-module-resolver@npm:^4.0.0": - version: 4.1.0 - resolution: "babel-plugin-module-resolver@npm:4.1.0" +"babel-plugin-module-resolver@npm:^5.0.0": + version: 5.0.0 + resolution: "babel-plugin-module-resolver@npm:5.0.0" dependencies: - find-babel-config: ^1.2.0 - glob: ^7.1.6 + find-babel-config: ^2.0.0 + glob: ^8.0.3 pkg-up: ^3.1.0 - reselect: ^4.0.0 - resolve: ^1.13.1 - checksum: 3907fba21ca3c66a081e01fbd16bb09c84781749db16aa57805becc376bb5ee8dc373d4b209613e1453d30ea6c836d13073e9e7b6d239ff1806dd1763a9ab18f + reselect: ^4.1.7 + resolve: ^1.22.1 + checksum: d6880e49fc8e7bac509a2c183b4303ee054a47a80032a59a6f7844bb468ebe5e333b5dc5378443afdab5839e2da2b31a6c8d9a985a0047cd076b82bb9161cc78 languageName: node linkType: hard @@ -8185,13 +8208,6 @@ __metadata: languageName: node linkType: hard -"bin-v8-flags-filter@npm:^1.1.2": - version: 1.2.0 - resolution: "bin-v8-flags-filter@npm:1.2.0" - checksum: 73a3130c818203d96d40b9a5c438e37bc1f6deb93b723fd2cc0ed7064ae028925ef5c8219122ead4707079e78285013aab4c79f60d3f15c4338bc7c385c7c0ec - languageName: node - linkType: hard - "binary-extensions@npm:^2.0.0": version: 2.2.0 resolution: "binary-extensions@npm:2.2.0" @@ -8786,15 +8802,15 @@ __metadata: languageName: node linkType: hard -"chrome-remote-interface@npm:^0.30.0": - version: 0.30.1 - resolution: "chrome-remote-interface@npm:0.30.1" +"chrome-remote-interface@npm:^0.32.2": + version: 0.32.2 + resolution: "chrome-remote-interface@npm:0.32.2" dependencies: commander: 2.11.x ws: ^7.2.0 bin: chrome-remote-interface: bin/client.js - checksum: c22db8acae60b51c213ba05cac9af6d7dc9c11d8e0c6c91ebfa0585433696280796afd69f6fdf9b68bd34eca03734446bd3f3679356c941d04a89f369458447a + checksum: 8a9fab249675eb1301b50ed99258aba8a844c07b03b5b4481d395ed72086aab80a492da81108e191a95da82a5930464c9d3ea359f331de56cd75f8e303f3a120 languageName: node linkType: hard @@ -9177,6 +9193,13 @@ __metadata: languageName: node linkType: hard +"commander@npm:^5.0.0": + version: 5.1.0 + resolution: "commander@npm:5.1.0" + checksum: 0b7fec1712fbcc6230fcb161d8d73b4730fa91a21dc089515489402ad78810547683f058e2a9835929c212fead1d6a6ade70db28bbb03edbc2829a9ab7d69447 + languageName: node + linkType: hard + "commander@npm:^7.2.0": version: 7.2.0 resolution: "commander@npm:7.2.0" @@ -10383,6 +10406,16 @@ __metadata: languageName: node linkType: hard +"des.js@npm:^1.0.1": + version: 1.1.0 + resolution: "des.js@npm:1.1.0" + dependencies: + inherits: ^2.0.1 + minimalistic-assert: ^1.0.0 + checksum: 0e9c1584b70d31e20f20a613fc9ef60fbc6a147dfec9e448a168794a4b97ac04d8dc47ea008f1fa93b0f8aaf7c1ead632a5e59ce1913a6079d2d244c9f5ebe33 + languageName: node + linkType: hard + "destroy@npm:1.2.0": version: 1.2.0 resolution: "destroy@npm:1.2.0" @@ -11082,13 +11115,6 @@ __metadata: languageName: node linkType: hard -"es6-promise@npm:^4.2.8": - version: 4.2.8 - resolution: "es6-promise@npm:4.2.8" - checksum: 95614a88873611cb9165a85d36afa7268af5c03a378b35ca7bda9508e1d4f1f6f19a788d4bc755b3fd37c8ebba40782018e02034564ff24c9d6fa37e959ad57d - languageName: node - linkType: hard - "escalade@npm:^3.1.1": version: 3.1.1 resolution: "escalade@npm:3.1.1" @@ -11502,21 +11528,21 @@ __metadata: languageName: node linkType: hard -"esotope-hammerhead@npm:0.6.1": - version: 0.6.1 - resolution: "esotope-hammerhead@npm:0.6.1" +"esotope-hammerhead@npm:0.6.2": + version: 0.6.2 + resolution: "esotope-hammerhead@npm:0.6.2" dependencies: "@types/estree": 0.0.46 - checksum: c20a2a8ab4f680ae9a8d9156264dd6bb49db0420d7aff335b40f804a1a9d876af527f6e78cbaa49157f5ebe0022b5a9e8ad7cdeafe22536a5db7c685b0a4020d + checksum: 741faadea09e9d88d8abd808ab03612dc1dee72d55a764767654839d73ef359214cc67cf3eeb4971b0e37034f933ebec1753ba71f5ba6f6a3553fbe71045a6fe languageName: node linkType: hard -"esotope-hammerhead@npm:0.6.2": - version: 0.6.2 - resolution: "esotope-hammerhead@npm:0.6.2" +"esotope-hammerhead@npm:0.6.4": + version: 0.6.4 + resolution: "esotope-hammerhead@npm:0.6.4" dependencies: "@types/estree": 0.0.46 - checksum: 741faadea09e9d88d8abd808ab03612dc1dee72d55a764767654839d73ef359214cc67cf3eeb4971b0e37034f933ebec1753ba71f5ba6f6a3553fbe71045a6fe + checksum: a3338d221f87428e667898afa2d1f3b8bd1802f9ce0401f86a5697751afeff2ef52eda28498e6b086aac8b33fa4bc1024e9dd2dc2a008ca4ca9f72c36dfc9654 languageName: node linkType: hard @@ -11995,13 +12021,13 @@ __metadata: languageName: node linkType: hard -"find-babel-config@npm:^1.2.0": - version: 1.2.0 - resolution: "find-babel-config@npm:1.2.0" +"find-babel-config@npm:^2.0.0": + version: 2.0.0 + resolution: "find-babel-config@npm:2.0.0" dependencies: - json5: ^0.5.1 - path-exists: ^3.0.0 - checksum: 0a1785d3da9f38637885d9d65f183aaa072f51a834f733035e9694e4d0f6983ae8c8e75cd4e08b92af6f595b3b490ee813a1c5a9b14740685aa836fa1e878583 + json5: ^2.1.1 + path-exists: ^4.0.0 + checksum: d110308b02fe6a6411a0cfb7fd50af6740fbf5093eada3d6ddacf99b07fc8eea4aa3475356484710a0032433029a21ce733bb3ef88fda1d6e35c29a3e4983014 languageName: node linkType: hard @@ -12436,6 +12462,18 @@ __metadata: languageName: node linkType: hard +"get-os-info@npm:^1.0.2": + version: 1.0.2 + resolution: "get-os-info@npm:1.0.2" + dependencies: + getos: ^3.2.1 + macos-release: ^3.0.1 + os-family: ^1.1.0 + windows-release: ^5.0.1 + checksum: 2b5a89b8ca4e537aead57de1abc02ab0be954b2d36846221670f42b5da599513170c19691ae9e81cb0359bb2642524850c53683ae3fcd16348b2e0406ee40cf4 + languageName: node + linkType: hard + "get-own-enumerable-property-symbols@npm:^3.0.0": version: 3.0.2 resolution: "get-own-enumerable-property-symbols@npm:3.0.2" @@ -12513,6 +12551,15 @@ __metadata: languageName: node linkType: hard +"getos@npm:^3.2.1": + version: 3.2.1 + resolution: "getos@npm:3.2.1" + dependencies: + async: ^3.2.0 + checksum: 42fd78a66d47cebd3e09de5566cc0044e034b08f4a000a310dbd89a77b02c65d8f4002554bfa495ea5bdc4fa9d515f5ac785a7cc474ba45383cc697f865eeaf1 + languageName: node + linkType: hard + "getpass@npm:^0.1.1": version: 0.1.7 resolution: "getpass@npm:0.1.7" @@ -12640,7 +12687,7 @@ __metadata: languageName: node linkType: hard -"glob@npm:^8.0.0": +"glob@npm:^8.0.0, glob@npm:^8.0.3": version: 8.1.0 resolution: "glob@npm:8.1.0" dependencies: @@ -13313,6 +13360,32 @@ __metadata: languageName: node linkType: hard +"http-status-codes@npm:^2.2.0": + version: 2.2.0 + resolution: "http-status-codes@npm:2.2.0" + checksum: 31e1d730856210445da0907d9b484629e69e4fe92ac032478a7aa4d89e5b215e2b4e75d7ebce40d0537b6850bd281b2f65c7cc36cc2677e5de056d6cea1045ce + languageName: node + linkType: hard + +"httpntlm@npm:^1.8.10": + version: 1.8.13 + resolution: "httpntlm@npm:1.8.13" + dependencies: + des.js: ^1.0.1 + httpreq: ">=0.4.22" + js-md4: ^0.3.2 + underscore: ~1.12.1 + checksum: 3107aee5b203d73194b5beb1c6ffa701e6cd56afb0f833f571d172a72649a7ec5928fb30fd51aea28df3ccb55adba634f95dcfe83caacb96e0ae5d049f6003a9 + languageName: node + linkType: hard + +"httpreq@npm:>=0.4.22": + version: 0.5.2 + resolution: "httpreq@npm:0.5.2" + checksum: 6afdaa8a0a48d9c8b460aad44d9a12783eba0436bfeb8f196fd7b0366c0749458ed3d4568326b585e98a6a36a21b69b993c46261272304fe50eb8b964f93b013 + languageName: node + linkType: hard + "https-proxy-agent@npm:^5.0.0": version: 5.0.1 resolution: "https-proxy-agent@npm:5.0.1" @@ -13675,27 +13748,6 @@ __metadata: languageName: node linkType: hard -"io-ts-types@npm:^0.5.15": - version: 0.5.16 - resolution: "io-ts-types@npm:0.5.16" - peerDependencies: - fp-ts: ^2.0.0 - io-ts: ^2.0.0 - monocle-ts: ^2.0.0 - newtype-ts: ^0.3.2 - checksum: 39b5c5639d7351264c8ee26f30fa71f7feb42225472fe8f3e489e5a50f1a2009b8e982dcdbfc977c69261f3fbd71cda6edcd7c2a7f96145cb1ba2879f65a1e77 - languageName: node - linkType: hard - -"io-ts@npm:^2.2.14": - version: 2.2.16 - resolution: "io-ts@npm:2.2.16" - peerDependencies: - fp-ts: ^2.5.0 - checksum: 1b5855682e413cc92cfb5c51d0bdb24c99e407ab4e67821a53078f5d8dedbc387840c5b240cb19dd5d542bbdd2e2317ece17674a674f3de561ca0f00ae765bba - languageName: node - linkType: hard - "ip-regex@npm:^2.1.0": version: 2.1.0 resolution: "ip-regex@npm:2.1.0" @@ -14074,6 +14126,15 @@ __metadata: languageName: node linkType: hard +"is-podman@npm:^1.0.1": + version: 1.0.1 + resolution: "is-podman@npm:1.0.1" + bin: + is-podman: cli.js + checksum: 9a72f5fced41c49d4ba6b11ef4d134b557b1773de7c5ecee8f875a2ce056d54e965a795a76d8fb5fd47889ff296093cce6063ad3ca83bd26bf4278828cec68e4 + languageName: node + linkType: hard + "is-potential-custom-element-name@npm:^1.0.1": version: 1.0.1 resolution: "is-potential-custom-element-name@npm:1.0.1" @@ -14275,16 +14336,6 @@ __metadata: languageName: node linkType: hard -"isomorphic-fetch@npm:^3.0.0": - version: 3.0.0 - resolution: "isomorphic-fetch@npm:3.0.0" - dependencies: - node-fetch: ^2.6.1 - whatwg-fetch: ^3.4.1 - checksum: e5ab79a56ce5af6ddd21265f59312ad9a4bc5a72cebc98b54797b42cb30441d5c5f8d17c5cd84a99e18101c8af6f90c081ecb8d12fd79e332be1778d58486d75 - languageName: node - linkType: hard - "isstream@npm:~0.1.2": version: 0.1.2 resolution: "isstream@npm:0.1.2" @@ -15007,6 +15058,13 @@ __metadata: languageName: node linkType: hard +"js-md4@npm:^0.3.2": + version: 0.3.2 + resolution: "js-md4@npm:0.3.2" + checksum: aa7b7cbd738b105f86fc0fa50ce2a7a6b6b329ff3f713d1bd38b12c2a72929bab5a16e658a03830faa8d656356aa89b4f77f1fbf05ffa8d095bda6d831441c2d + languageName: node + linkType: hard + "js-tokens@npm:^3.0.0": version: 3.0.2 resolution: "js-tokens@npm:3.0.2" @@ -15247,7 +15305,7 @@ __metadata: languageName: node linkType: hard -"json5@npm:2.x, json5@npm:^2.1.0, json5@npm:^2.1.2, json5@npm:^2.2.0": +"json5@npm:2.x, json5@npm:^2.1.2, json5@npm:^2.2.0": version: 2.2.1 resolution: "json5@npm:2.2.1" bin: @@ -15256,15 +15314,6 @@ __metadata: languageName: node linkType: hard -"json5@npm:^0.5.1": - version: 0.5.1 - resolution: "json5@npm:0.5.1" - bin: - json5: lib/cli.js - checksum: 9b85bf06955b23eaa4b7328aa8892e3887e81ca731dd27af04a5f5f1458fbc5e1de57a24442e3272f8a888dd1abe1cb68eb693324035f6b3aeba4fcab7667d62 - languageName: node - linkType: hard - "json5@npm:^1.0.1": version: 1.0.1 resolution: "json5@npm:1.0.1" @@ -15276,6 +15325,15 @@ __metadata: languageName: node linkType: hard +"json5@npm:^2.1.1, json5@npm:^2.2.2": + version: 2.2.3 + resolution: "json5@npm:2.2.3" + bin: + json5: lib/cli.js + checksum: 2a7436a93393830bce797d4626275152e37e877b265e94ca69c99e3d20c2b9dab021279146a39cdb700e71b2dd32a4cebd1514cd57cee102b1af906ce5040349 + languageName: node + linkType: hard + "jsonfile@npm:^4.0.0": version: 4.0.0 resolution: "jsonfile@npm:4.0.0" @@ -15315,24 +15373,6 @@ __metadata: languageName: node linkType: hard -"jsonwebtoken@npm:^8.5.1": - version: 8.5.1 - resolution: "jsonwebtoken@npm:8.5.1" - dependencies: - jws: ^3.2.2 - lodash.includes: ^4.3.0 - lodash.isboolean: ^3.0.3 - lodash.isinteger: ^4.0.4 - lodash.isnumber: ^3.0.3 - lodash.isplainobject: ^4.0.6 - lodash.isstring: ^4.0.1 - lodash.once: ^4.0.0 - ms: ^2.1.1 - semver: ^5.6.0 - checksum: 93c9e3f23c59b758ac88ba15f4e4753b3749dfce7a6f7c40fb86663128a1e282db085eec852d4e0cbca4cefdcd3a8275ee255dbd08fcad0df26ad9f6e4cc853a - languageName: node - linkType: hard - "jsonwebtoken@npm:^9.0.0": version: 9.0.0 resolution: "jsonwebtoken@npm:9.0.0" @@ -15880,20 +15920,6 @@ __metadata: languageName: node linkType: hard -"lodash.includes@npm:^4.3.0": - version: 4.3.0 - resolution: "lodash.includes@npm:4.3.0" - checksum: 71092c130515a67ab3bd928f57f6018434797c94def7f46aafa417771e455ce3a4834889f4267b17887d7f75297dfabd96231bf704fd2b8c5096dc4a913568b6 - languageName: node - linkType: hard - -"lodash.isboolean@npm:^3.0.3": - version: 3.0.3 - resolution: "lodash.isboolean@npm:3.0.3" - checksum: b70068b4a8b8837912b54052557b21fc4774174e3512ed3c5b94621e5aff5eb6c68089d0a386b7e801d679cd105d2e35417978a5e99071750aa2ed90bffd0250 - languageName: node - linkType: hard - "lodash.isequal@npm:^4.5.0": version: 4.5.0 resolution: "lodash.isequal@npm:4.5.0" @@ -15901,13 +15927,6 @@ __metadata: languageName: node linkType: hard -"lodash.isinteger@npm:^4.0.4": - version: 4.0.4 - resolution: "lodash.isinteger@npm:4.0.4" - checksum: 6034821b3fc61a2ffc34e7d5644bb50c5fd8f1c0121c554c21ac271911ee0c0502274852845005f8651d51e199ee2e0cfebfe40aaa49c7fe617f603a8a0b1691 - languageName: node - linkType: hard - "lodash.ismatch@npm:^4.4.0": version: 4.4.0 resolution: "lodash.ismatch@npm:4.4.0" @@ -15915,27 +15934,6 @@ __metadata: languageName: node linkType: hard -"lodash.isnumber@npm:^3.0.3": - version: 3.0.3 - resolution: "lodash.isnumber@npm:3.0.3" - checksum: 913784275b565346255e6ae6a6e30b760a0da70abc29f3e1f409081585875105138cda4a429ff02577e1bc0a7ae2a90e0a3079a37f3a04c3d6c5aaa532f4cab2 - languageName: node - linkType: hard - -"lodash.isplainobject@npm:^4.0.6": - version: 4.0.6 - resolution: "lodash.isplainobject@npm:4.0.6" - checksum: 29c6351f281e0d9a1d58f1a4c8f4400924b4c79f18dfc4613624d7d54784df07efaff97c1ff2659f3e085ecf4fff493300adc4837553104cef2634110b0d5337 - languageName: node - linkType: hard - -"lodash.isstring@npm:^4.0.1": - version: 4.0.1 - resolution: "lodash.isstring@npm:4.0.1" - checksum: eaac87ae9636848af08021083d796e2eea3d02e80082ab8a9955309569cb3a463ce97fd281d7dc119e402b2e7d8c54a23914b15d2fc7fff56461511dc8937ba0 - languageName: node - linkType: hard - "lodash.mapvalues@npm:^4.6.0": version: 4.6.0 resolution: "lodash.mapvalues@npm:4.6.0" @@ -15964,13 +15962,6 @@ __metadata: languageName: node linkType: hard -"lodash.once@npm:^4.0.0": - version: 4.1.1 - resolution: "lodash.once@npm:4.1.1" - checksum: d768fa9f9b4e1dc6453be99b753906f58990e0c45e7b2ca5a3b40a33111e5d17f6edf2f768786e2716af90a8e78f8f91431ab8435f761fef00f9b0c256f6d245 - languageName: node - linkType: hard - "lodash.sortby@npm:^4.7.0": version: 4.7.0 resolution: "lodash.sortby@npm:4.7.0" @@ -16168,6 +16159,13 @@ __metadata: languageName: node linkType: hard +"macos-release@npm:^3.0.1": + version: 3.2.0 + resolution: "macos-release@npm:3.2.0" + checksum: e780af4a8dcfdb4d7b5e717f866baf19f81798772b1f422ca5409c0a6b39baeb80827976fa498b8582409100c2d8c10cb89498dd557d777218cb40733c771843 + languageName: node + linkType: hard + "magic-string@npm:^0.25.0, magic-string@npm:^0.25.7": version: 0.25.9 resolution: "magic-string@npm:0.25.9" @@ -16804,7 +16802,7 @@ __metadata: languageName: node linkType: hard -"moment@npm:>=1.6.0, moment@npm:^2.14.1, moment@npm:^2.26.0, moment@npm:^2.29.1, moment@npm:^2.29.3": +"moment@npm:>=1.6.0, moment@npm:^2.14.1, moment@npm:^2.26.0, moment@npm:^2.29.1, moment@npm:^2.29.4": version: 2.29.4 resolution: "moment@npm:2.29.4" checksum: 0ec3f9c2bcba38dc2451b1daed5daded747f17610b92427bebe1d08d48d8b7bdd8d9197500b072d14e326dd0ccf3e326b9e3d07c5895d3d49e39b6803b76e80e @@ -16837,15 +16835,6 @@ __metadata: languageName: node linkType: hard -"monocle-ts@npm:^2.3.5": - version: 2.3.13 - resolution: "monocle-ts@npm:2.3.13" - peerDependencies: - fp-ts: ^2.5.0 - checksum: dddfa5706fe1fdb068606f5ac5215a00e723d155407de491bb53591264e281ec0ea1735759db93686d9daf676b105660bf2c656ebcd51626a4bfbb8de9accb3c - languageName: node - linkType: hard - "morgan@npm:^1.10.0": version: 1.10.0 resolution: "morgan@npm:1.10.0" @@ -16958,16 +16947,6 @@ __metadata: languageName: node linkType: hard -"newtype-ts@npm:^0.3.4": - version: 0.3.5 - resolution: "newtype-ts@npm:0.3.5" - peerDependencies: - fp-ts: ^2.0.0 - monocle-ts: ^2.0.0 - checksum: 3168c0386313156f4ecf9160eec48d5506aa372d3406f6a05fde9f75b8f9549b149dea53574e9b2f201190c01fe49af8c9554481b319f0b616a4413f89af3d16 - languageName: node - linkType: hard - "nise@npm:^5.1.1": version: 5.1.1 resolution: "nise@npm:5.1.1" @@ -17605,7 +17584,7 @@ __metadata: languageName: node linkType: hard -"os-family@npm:^1.0.0": +"os-family@npm:^1.0.0, os-family@npm:^1.1.0": version: 1.1.0 resolution: "os-family@npm:1.1.0" checksum: 2bd105181d87027959acfe092da8c52a77463c2837716e5a7a78fda1e0e10e1baa2f71130a9b079f14b6f848d4ddfd905b487217ed9ada69b92e7314d65bbf92 @@ -19500,6 +19479,13 @@ __metadata: languageName: node linkType: hard +"querystringify@npm:^2.1.1": + version: 2.2.0 + resolution: "querystringify@npm:2.2.0" + checksum: 5641ea231bad7ef6d64d9998faca95611ed4b11c2591a8cae741e178a974f6a8e0ebde008475259abe1621cb15e692404e6b6626e927f7b849d5c09392604b15 + languageName: node + linkType: hard + "queue-microtask@npm:^1.2.2": version: 1.2.3 resolution: "queue-microtask@npm:1.2.3" @@ -20423,13 +20409,20 @@ __metadata: languageName: node linkType: hard -"reselect@npm:^4.0.0, reselect@npm:^4.1.5": +"reselect@npm:^4.1.5": version: 4.1.6 resolution: "reselect@npm:4.1.6" checksum: 3ea1058422904063ec93c8f4693fe33dcb2178bbf417ace8db5b2c797a5875cf357d9308d11ed3942ee22507dd34ecfbf1f3a21340a4f31c206cab1d36ceef31 languageName: node linkType: hard +"reselect@npm:^4.1.7": + version: 4.1.8 + resolution: "reselect@npm:4.1.8" + checksum: a4ac87cedab198769a29be92bc221c32da76cfdad6911eda67b4d3e7136dca86208c3b210e31632eae31ebd2cded18596f0dd230d3ccc9e978df22f233b5583e + languageName: node + linkType: hard + "resolve-cwd@npm:^1.0.0": version: 1.0.0 resolution: "resolve-cwd@npm:1.0.0" @@ -20504,7 +20497,7 @@ __metadata: languageName: node linkType: hard -"resolve@npm:^1.0.0, resolve@npm:^1.1.7, resolve@npm:^1.10.0, resolve@npm:^1.12.0, resolve@npm:^1.13.1, resolve@npm:^1.14.2, resolve@npm:^1.19.0, resolve@npm:^1.20.0, resolve@npm:^1.22.0, resolve@npm:^1.22.1": +"resolve@npm:^1.0.0, resolve@npm:^1.1.7, resolve@npm:^1.10.0, resolve@npm:^1.12.0, resolve@npm:^1.14.2, resolve@npm:^1.19.0, resolve@npm:^1.20.0, resolve@npm:^1.22.0, resolve@npm:^1.22.1": version: 1.22.1 resolution: "resolve@npm:1.22.1" dependencies: @@ -20530,7 +20523,7 @@ __metadata: languageName: node linkType: hard -"resolve@patch:resolve@^1.0.0#~builtin, resolve@patch:resolve@^1.1.7#~builtin, resolve@patch:resolve@^1.10.0#~builtin, resolve@patch:resolve@^1.12.0#~builtin, resolve@patch:resolve@^1.13.1#~builtin, resolve@patch:resolve@^1.14.2#~builtin, resolve@patch:resolve@^1.19.0#~builtin, resolve@patch:resolve@^1.20.0#~builtin, resolve@patch:resolve@^1.22.0#~builtin, resolve@patch:resolve@^1.22.1#~builtin": +"resolve@patch:resolve@^1.0.0#~builtin, resolve@patch:resolve@^1.1.7#~builtin, resolve@patch:resolve@^1.10.0#~builtin, resolve@patch:resolve@^1.12.0#~builtin, resolve@patch:resolve@^1.14.2#~builtin, resolve@patch:resolve@^1.19.0#~builtin, resolve@patch:resolve@^1.20.0#~builtin, resolve@patch:resolve@^1.22.0#~builtin, resolve@patch:resolve@^1.22.1#~builtin": version: 1.22.1 resolution: "resolve@patch:resolve@npm%3A1.22.1#~builtin::version=1.22.1&hash=c3c19d" dependencies: @@ -20955,6 +20948,17 @@ __metadata: languageName: node linkType: hard +"semver@npm:7.5.3": + version: 7.5.3 + resolution: "semver@npm:7.5.3" + dependencies: + lru-cache: ^6.0.0 + bin: + semver: bin/semver.js + checksum: 9d58db16525e9f749ad0a696a1f27deabaa51f66e91d2fa2b0db3de3e9644e8677de3b7d7a03f4c15bc81521e0c3916d7369e0572dbde250d9bedf5194e2a8a7 + languageName: node + linkType: hard + "semver@npm:7.x, semver@npm:^7.1.1, semver@npm:^7.1.3, semver@npm:^7.3.2, semver@npm:^7.3.4, semver@npm:^7.3.5, semver@npm:^7.3.7": version: 7.3.7 resolution: "semver@npm:7.3.7" @@ -20986,6 +20990,17 @@ __metadata: languageName: node linkType: hard +"semver@npm:^7.5.3": + version: 7.5.4 + resolution: "semver@npm:7.5.4" + dependencies: + lru-cache: ^6.0.0 + bin: + semver: bin/semver.js + checksum: 12d8ad952fa353b0995bf180cdac205a4068b759a140e5d3c608317098b3575ac2f1e09182206bf2eb26120e1c0ed8fb92c48c592f6099680de56bb071423ca3 + languageName: node + linkType: hard + "send@npm:0.18.0": version: 0.18.0 resolution: "send@npm:0.18.0" @@ -21066,6 +21081,13 @@ __metadata: languageName: node linkType: hard +"set-cookie-parser@npm:^2.5.1": + version: 2.6.0 + resolution: "set-cookie-parser@npm:2.6.0" + checksum: bf11ebc594c53d84588f1b4c04f1b8ce14e0498b1c011b3d76b5c6d5aac481bbc3f7c5260ec4ce99bdc1d9aed19f9fc315e73166a36ca74d0f12349a73f6bdc9 + languageName: node + linkType: hard + "setprototypeof@npm:1.1.0": version: 1.1.0 resolution: "setprototypeof@npm:1.1.0" @@ -22471,9 +22493,9 @@ __metadata: languageName: node linkType: hard -"testcafe-browser-tools@npm:2.0.23": - version: 2.0.23 - resolution: "testcafe-browser-tools@npm:2.0.23" +"testcafe-browser-tools@npm:2.0.26": + version: 2.0.26 + resolution: "testcafe-browser-tools@npm:2.0.26" dependencies: array-find: ^1.0.0 debug: ^4.3.1 @@ -22492,22 +22514,23 @@ __metadata: pinkie: ^2.0.1 read-file-relative: ^1.2.0 which-promise: ^1.0.0 - checksum: d78b9100d87495f99e168a28cc4b803f44ea5d441275f8329e4cf2f22a2e0c83e1a0275496d418aff9f2bb56a9a30f89fcdd2289a1e59dacda98c10af36ce5f6 + checksum: 764c31074ed83f4f3efb6115cb52e8cee2c58ac6e091a7c5d5a58c993b07be24b4a2b43d47c9b50625a260a6daee69ce6b9231273f79117625c4b957193d4752 languageName: node linkType: hard -"testcafe-hammerhead@npm:24.5.19": - version: 24.5.19 - resolution: "testcafe-hammerhead@npm:24.5.19" +"testcafe-hammerhead@npm:31.4.11": + version: 31.4.11 + resolution: "testcafe-hammerhead@npm:31.4.11" dependencies: - acorn-hammerhead: 0.6.1 - asar: ^2.0.1 + "@electron/asar": ^3.2.3 + acorn-hammerhead: 0.6.2 bowser: 1.6.0 crypto-md5: ^1.0.0 css: 2.2.3 debug: 4.3.1 - esotope-hammerhead: 0.6.1 + esotope-hammerhead: 0.6.4 http-cache-semantics: ^4.1.0 + httpntlm: ^1.8.10 iconv-lite: 0.5.1 lodash: ^4.17.20 lru-cache: 2.6.3 @@ -22520,11 +22543,11 @@ __metadata: parse5: 2.2.3 pinkie: 2.0.4 read-file-relative: ^1.2.0 - semver: 5.5.0 - tough-cookie: 4.0.0 + semver: 7.5.3 + tough-cookie: 4.1.3 tunnel-agent: 0.6.0 - webauth: ^1.1.0 - checksum: 2fe8ed6c4b197401e70636c1bf9efdbd2797f03fdca75915b12a611d56157a423a940e2a5dcb51b31a7eb13dd92c4131acd673938f50caa701e8e82fd4c627d2 + ws: ^7.4.6 + checksum: b1085d24ba09fbf412b11d8b4d6113b84d0270bf9d0817a881cde83d91bf41709002914757c88c874f11664de78f0dcf083bed3042f299312e25ce1bbc0fc6ec languageName: node linkType: hard @@ -22560,9 +22583,9 @@ __metadata: languageName: node linkType: hard -"testcafe-legacy-api@npm:5.1.4": - version: 5.1.4 - resolution: "testcafe-legacy-api@npm:5.1.4" +"testcafe-legacy-api@npm:5.1.6": + version: 5.1.6 + resolution: "testcafe-legacy-api@npm:5.1.6" dependencies: async: 3.2.3 dedent: ^0.6.0 @@ -22578,24 +22601,7 @@ __metadata: read-file-relative: ^1.2.0 strip-bom: ^2.0.0 testcafe-hammerhead: ">=19.4.0" - checksum: f2d85545979e8bc8973a302d3ffbdbb868e43a9b03631ac1766767193d9e4ca9f13a850af130bbf1a3a29d2d58fbd6069829116fad4bf5b329ec5d1249ace0e8 - languageName: node - linkType: hard - -"testcafe-reporter-dashboard@npm:1.0.0-rc.1": - version: 1.0.0-rc.1 - resolution: "testcafe-reporter-dashboard@npm:1.0.0-rc.1" - dependencies: - es6-promise: ^4.2.8 - io-ts: ^2.2.14 - io-ts-types: ^0.5.15 - isomorphic-fetch: ^3.0.0 - jsonwebtoken: ^8.5.1 - monocle-ts: ^2.3.5 - newtype-ts: ^0.3.4 - semver: ^5.6.0 - uuid: 3.3.3 - checksum: 938bc2e0b0448b438f6a510eaf59088c69f0295a1093dbdf0b3fb096bf6c7c1213dfd741d3cd03124b95be629eebd6adf991a2f5e0c6af49a85d3cdd1965bfdd + checksum: b0cb316bf13c53e2b0b411eb6a313fd4a3f8222f8fdfe3aec717b0d0d2cd30ce46a4cdc20b819c987e2fdc11c389e13032518b6f63b1613f8b1b12d387778148 languageName: node linkType: hard @@ -22606,24 +22612,24 @@ __metadata: languageName: node linkType: hard -"testcafe-reporter-list@npm:^2.1.0": - version: 2.1.0 - resolution: "testcafe-reporter-list@npm:2.1.0" - checksum: d1f62e27b6dffd23f833bd3e83e30266b5ea12e24d36ee0937e2c36bf917eb534d7991bc7db3ea378fe1ecae289bc2e009556ed7caedc8d540820cd036d5b575 +"testcafe-reporter-list@npm:^2.2.0": + version: 2.2.0 + resolution: "testcafe-reporter-list@npm:2.2.0" + checksum: ba473ca217246fcd5757200ae65785925188497a65c9b1e0b56a8db2e186a176daf51e14e2b42471d1ce0abcaad89cadea42a01a59779570f770b6beb0e60530 languageName: node linkType: hard -"testcafe-reporter-minimal@npm:^2.1.0": - version: 2.1.0 - resolution: "testcafe-reporter-minimal@npm:2.1.0" - checksum: 5210869339bdf0bba3afbbc1359b604df369c6a520ac0caf1f23b72818a01cafc999c29fa05f3e1b7a14a22c0c5495b1dede7de221c0087e894488e8e5843445 +"testcafe-reporter-minimal@npm:^2.2.0": + version: 2.2.0 + resolution: "testcafe-reporter-minimal@npm:2.2.0" + checksum: 1919a793c0fa9c834a78c13cb3c568a6325742303eb9de50242d12dfe7fd26d77f6fe155c59d24408fcb73e8b14cba345e09ed0db030ce7cfa0e04d7a23bbb3b languageName: node linkType: hard -"testcafe-reporter-spec@npm:^2.1.1": - version: 2.1.1 - resolution: "testcafe-reporter-spec@npm:2.1.1" - checksum: 699d7811bbc2c1fcd9720718f5873ae6577dd7b5a8b97119bb1afc2014e6d36ae92e88329433de253214291c33b6818efc7a44c31b98cb089ecd9504909a56c5 +"testcafe-reporter-spec@npm:^2.2.0": + version: 2.2.0 + resolution: "testcafe-reporter-spec@npm:2.2.0" + checksum: a3992a858363decdb887be9754a3656b107c916dcd024b4fa3227a471c28d975c95ca3508acc22c27f2a3c5e18939c7f96b7eddcc57b6116e3f1384dc21c35fa languageName: node linkType: hard @@ -22641,9 +22647,16 @@ __metadata: languageName: node linkType: hard -"testcafe@npm:^1.18.1": - version: 1.19.0 - resolution: "testcafe@npm:1.19.0" +"testcafe-selector-generator@npm:^0.1.0": + version: 0.1.0 + resolution: "testcafe-selector-generator@npm:0.1.0" + checksum: b5bbbe57739b6f1c5258c7dcd0a60020b0d886929e78155bc3877e22706b14cf0468f6cc801ff15857ea02b047a8f0e155521b5a588b71097ab2ce3e09eaed15 + languageName: node + linkType: hard + +"testcafe@npm:^3.1.0": + version: 3.1.0 + resolution: "testcafe@npm:3.1.0" dependencies: "@babel/core": ^7.12.1 "@babel/plugin-proposal-async-generator-functions": ^7.12.1 @@ -22661,18 +22674,17 @@ __metadata: "@babel/preset-flow": ^7.12.1 "@babel/preset-react": ^7.12.1 "@babel/runtime": ^7.12.5 - "@miherlosev/esm": 3.2.26 + "@devexpress/bin-v8-flags-filter": ^1.3.0 "@types/node": ^12.20.10 async-exit-hook: ^1.1.2 - babel-plugin-module-resolver: ^4.0.0 + babel-plugin-module-resolver: ^5.0.0 babel-plugin-syntax-trailing-function-commas: ^6.22.0 - bin-v8-flags-filter: ^1.1.2 bowser: ^2.8.1 callsite: ^1.0.0 callsite-record: ^4.0.0 chai: 4.3.4 chalk: ^2.3.0 - chrome-remote-interface: ^0.30.0 + chrome-remote-interface: ^0.32.2 coffeescript: ^2.3.1 commander: ^8.3.0 debug: ^4.3.1 @@ -22686,22 +22698,25 @@ __metadata: endpoint-utils: ^1.0.2 error-stack-parser: ^1.3.6 execa: ^4.0.3 + get-os-info: ^1.0.2 globby: ^11.0.4 graceful-fs: ^4.1.11 graphlib: ^2.1.5 + http-status-codes: ^2.2.0 humanize-duration: ^3.25.0 import-lazy: ^3.1.0 indent-string: ^1.2.2 is-ci: ^1.0.10 is-docker: ^2.0.0 is-glob: ^2.0.1 + is-podman: ^1.0.1 is-stream: ^2.0.0 - json5: ^2.1.0 + json5: ^2.2.2 lodash: ^4.17.13 log-update-async-hook: ^2.0.7 make-dir: ^3.0.0 mime-db: ^1.41.0 - moment: ^2.29.3 + moment: ^2.29.4 moment-duration-format-commonjs: ^1.0.0 mustache: ^2.1.2 nanoid: ^3.1.31 @@ -22719,27 +22734,29 @@ __metadata: resolve-cwd: ^1.0.0 resolve-from: ^4.0.0 sanitize-filename: ^1.6.0 - semver: ^5.6.0 + semver: ^7.5.3 + set-cookie-parser: ^2.5.1 source-map-support: ^0.5.16 strip-bom: ^2.0.0 - testcafe-browser-tools: 2.0.23 - testcafe-hammerhead: 24.5.19 - testcafe-legacy-api: 5.1.4 - testcafe-reporter-dashboard: 1.0.0-rc.1 + testcafe-browser-tools: 2.0.26 + testcafe-hammerhead: 31.4.11 + testcafe-legacy-api: 5.1.6 testcafe-reporter-json: ^2.1.0 - testcafe-reporter-list: ^2.1.0 - testcafe-reporter-minimal: ^2.1.0 - testcafe-reporter-spec: ^2.1.1 + testcafe-reporter-list: ^2.2.0 + testcafe-reporter-minimal: ^2.2.0 + testcafe-reporter-spec: ^2.2.0 testcafe-reporter-xunit: ^2.2.1 testcafe-safe-storage: ^1.1.1 + testcafe-selector-generator: ^0.1.0 time-limit-promise: ^1.0.2 tmp: 0.0.28 tree-kill: ^1.2.2 - typescript: ^3.3.3 + typescript: 4.7.4 unquote: ^1.1.1 + url-to-options: ^2.0.0 bin: testcafe: bin/testcafe-with-v8-flag-filter.js - checksum: 22e458c619a234f6f08da8ab4a9bf38e9560feba58b9281b604fd22b8be5e56112ade8662a0cc229fd3e2c5428182554d796bc5d38ee09008323a67ec184f916 + checksum: 668b368f0c1b17cceb0db85a84217c5d681014fa05ffdf167a40fed5fe34c70d3aac91659bee28f7360d7a1c24082b3ca4872e7742c7ef29610cb688c71fc3e0 languageName: node linkType: hard @@ -22921,6 +22938,18 @@ __metadata: languageName: node linkType: hard +"tough-cookie@npm:4.1.3": + version: 4.1.3 + resolution: "tough-cookie@npm:4.1.3" + dependencies: + psl: ^1.1.33 + punycode: ^2.1.1 + universalify: ^0.2.0 + url-parse: ^1.5.3 + checksum: c9226afff36492a52118432611af083d1d8493a53ff41ec4ea48e5b583aec744b989e4280bcf476c910ec1525a89a4a0f1cae81c08b18fb2ec3a9b3a72b91dcc + languageName: node + linkType: hard + "tough-cookie@npm:^3.0.1": version: 3.0.1 resolution: "tough-cookie@npm:3.0.1" @@ -23419,6 +23448,13 @@ __metadata: languageName: node linkType: hard +"underscore@npm:~1.12.1": + version: 1.12.1 + resolution: "underscore@npm:1.12.1" + checksum: ec327603aa112b99fe9d74cd9bf3b3b7451465a9d2610ceab269a532e3f191650ab017903be34dc86fe406a11d04d8905a3b04dd4c129493e51bee09a3f3074c + languageName: node + linkType: hard + "underscore@npm:~1.13.2": version: 1.13.6 resolution: "underscore@npm:1.13.6" @@ -23498,6 +23534,13 @@ __metadata: languageName: node linkType: hard +"universalify@npm:^0.2.0": + version: 0.2.0 + resolution: "universalify@npm:0.2.0" + checksum: e86134cb12919d177c2353196a4cc09981524ee87abf621f7bc8d249dbbbebaec5e7d1314b96061497981350df786e4c5128dbf442eba104d6e765bc260678b5 + languageName: node + linkType: hard + "universalify@npm:^2.0.0": version: 2.0.0 resolution: "universalify@npm:2.0.0" @@ -23601,6 +23644,23 @@ __metadata: languageName: node linkType: hard +"url-parse@npm:^1.5.3": + version: 1.5.10 + resolution: "url-parse@npm:1.5.10" + dependencies: + querystringify: ^2.1.1 + requires-port: ^1.0.0 + checksum: fbdba6b1d83336aca2216bbdc38ba658d9cfb8fc7f665eb8b17852de638ff7d1a162c198a8e4ed66001ddbf6c9888d41e4798912c62b4fd777a31657989f7bdf + languageName: node + linkType: hard + +"url-to-options@npm:^2.0.0": + version: 2.0.0 + resolution: "url-to-options@npm:2.0.0" + checksum: 104741b13c2a1388bbd6a488fa99f70535931cb9a81a8aa039037fa5264174e2cdc9eae6b99442f7314003cea6205a6769766febc4893c9ac93d409ee356d885 + languageName: node + linkType: hard + "url@npm:0.10.3": version: 0.10.3 resolution: "url@npm:0.10.3" @@ -23660,15 +23720,6 @@ __metadata: languageName: node linkType: hard -"uuid@npm:3.3.3": - version: 3.3.3 - resolution: "uuid@npm:3.3.3" - bin: - uuid: ./bin/uuid - checksum: 21133d0e8a85e607f59a66913bf4f1dd79bdc4a80979a872b913f7ec75f530255edfe8bc6b69ce32017b8367f0d60a8b24ccd2af99156dc1ef2b8b9fe0ec8065 - languageName: node - linkType: hard - "uuid@npm:8.0.0": version: 8.0.0 resolution: "uuid@npm:8.0.0" @@ -24039,7 +24090,7 @@ __metadata: languageName: node linkType: hard -"whatwg-fetch@npm:^3.4.1, whatwg-fetch@npm:^3.6.2": +"whatwg-fetch@npm:^3.6.2": version: 3.6.2 resolution: "whatwg-fetch@npm:3.6.2" checksum: ee976b7249e7791edb0d0a62cd806b29006ad7ec3a3d89145921ad8c00a3a67e4be8f3fb3ec6bc7b58498724fd568d11aeeeea1f7827e7e1e5eae6c8a275afed @@ -24176,6 +24227,15 @@ __metadata: languageName: node linkType: hard +"windows-release@npm:^5.0.1": + version: 5.1.1 + resolution: "windows-release@npm:5.1.1" + dependencies: + execa: ^5.1.1 + checksum: 8d15388ccfcbacb96d551f4a692a0a0930a12d2283d140d0a00ea0f6c4f950907cb8055a2cff8650d8bcd5125585338ff0f21a0d7661a30c1d67b6729d13b6b8 + languageName: node + linkType: hard + "winston-transport@npm:^4.3.0, winston-transport@npm:^4.5.0": version: 4.5.0 resolution: "winston-transport@npm:4.5.0" From 35b0030ea5f7b9ede12f2ec7d02fcf09d6cd406f Mon Sep 17 00:00:00 2001 From: Arik Smith <--global> Date: Thu, 3 Aug 2023 11:01:28 -0400 Subject: [PATCH 202/251] changeset: Migrates to Testcafe version 3, and reduces test concurrency --- .changeset/little-sheep-fry.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/little-sheep-fry.md diff --git a/.changeset/little-sheep-fry.md b/.changeset/little-sheep-fry.md new file mode 100644 index 000000000..fe96c22cc --- /dev/null +++ b/.changeset/little-sheep-fry.md @@ -0,0 +1,5 @@ +--- +'@cloud-carbon-footprint/integration-tests': major +--- + +Migrates to Testcafe version 3, and reduces test concurrency From a26e380c9564fe72cbc935621c9ae9d38c774160 Mon Sep 17 00:00:00 2001 From: Cam Casher Date: Fri, 4 Aug 2023 09:19:12 -0400 Subject: [PATCH 203/251] fix: makes include estimates variable true by default --- packages/common/src/Config.ts | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/packages/common/src/Config.ts b/packages/common/src/Config.ts index d2e30331c..520fbc9d1 100644 --- a/packages/common/src/Config.ts +++ b/packages/common/src/Config.ts @@ -158,9 +158,10 @@ const getEnvVar = (envVar: string): string => { const getConfig = (): CCFConfig => ({ AWS: { - INCLUDE_ESTIMATES: + INCLUDE_ESTIMATES: !( !!process.env.AWS_INCLUDE_ESTIMATES && - process.env.AWS_INCLUDE_ESTIMATES !== 'false', + process.env.AWS_INCLUDE_ESTIMATES === 'false' + ), USE_BILLING_DATA: !!process.env.AWS_USE_BILLING_DATA && process.env.AWS_USE_BILLING_DATA !== 'false', @@ -246,9 +247,10 @@ const getConfig = (): CCFConfig => ({ USE_CARBON_FREE_ENERGY_PERCENTAGE: !!process.env.GCP_USE_CARBON_FREE_ENERGY_PERCENTAGE && process.env.GCP_USE_CARBON_FREE_ENERGY_PERCENTAGE !== 'false', - INCLUDE_ESTIMATES: + INCLUDE_ESTIMATES: !( !!process.env.GCP_INCLUDE_ESTIMATES && - process.env.GCP_INCLUDE_ESTIMATES !== 'false', + process.env.GCP_INCLUDE_ESTIMATES !== 'false' + ), USE_BILLING_DATA: !!process.env.GCP_USE_BILLING_DATA && process.env.GCP_USE_BILLING_DATA !== 'false', @@ -263,9 +265,10 @@ const getConfig = (): CCFConfig => ({ RESOURCE_TAG_NAMES: JSON.parse(getGCPResourceTagNames()), }, AZURE: { - INCLUDE_ESTIMATES: + INCLUDE_ESTIMATES: !( !!process.env.AZURE_INCLUDE_ESTIMATES && - process.env.AZURE_INCLUDE_ESTIMATES !== 'false', + process.env.AZURE_INCLUDE_ESTIMATES !== 'false' + ), USE_BILLING_DATA: !!process.env.AZURE_USE_BILLING_DATA && process.env.AZURE_USE_BILLING_DATA !== 'false', From 00d4b21f045c00d071d0da52a59994547e8112cd Mon Sep 17 00:00:00 2001 From: Matthew Griffin Date: Thu, 3 Aug 2023 08:00:41 +0100 Subject: [PATCH 204/251] [1193] Fix issue with concatentation of data from multiple footprint requests --- packages/client/src/utils/hooks/FootprintServiceHook.test.ts | 4 ++++ packages/client/src/utils/hooks/FootprintServiceHook.ts | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/client/src/utils/hooks/FootprintServiceHook.test.ts b/packages/client/src/utils/hooks/FootprintServiceHook.test.ts index 6a1d6f54a..5297c86a1 100644 --- a/packages/client/src/utils/hooks/FootprintServiceHook.test.ts +++ b/packages/client/src/utils/hooks/FootprintServiceHook.test.ts @@ -293,6 +293,10 @@ describe('FootprintServiceHook', () => { loading: false, error: null, }) + expect(result.current.data[0].serviceEstimates.length).toEqual(2) + expect(result.current.data[0].serviceEstimates[1]).toEqual( + mockEstimateTwo.serviceEstimates[0], + ) }) }) }) diff --git a/packages/client/src/utils/hooks/FootprintServiceHook.ts b/packages/client/src/utils/hooks/FootprintServiceHook.ts index c0b6aec89..254655fe7 100644 --- a/packages/client/src/utils/hooks/FootprintServiceHook.ts +++ b/packages/client/src/utils/hooks/FootprintServiceHook.ts @@ -123,7 +123,7 @@ const concatenateResults = (estimates, newEstimates) => { ) if (newDateExists) { - const elementIndex = updatedEstimates.find((estimate) => + const elementIndex = updatedEstimates.findIndex((estimate) => moment .utc(estimate.timestamp) .isSame(moment.utc(newEstimate.timestamp)), From 4ad7583630e290456eebea271155e782b1963310 Mon Sep 17 00:00:00 2001 From: Matthew Griffin Date: Thu, 3 Aug 2023 08:04:57 +0100 Subject: [PATCH 205/251] [1193] Copy change to create-app folder --- .../packages/client/src/utils/hooks/FootprintServiceHook.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/create-app/templates/default-app/packages/client/src/utils/hooks/FootprintServiceHook.ts b/packages/create-app/templates/default-app/packages/client/src/utils/hooks/FootprintServiceHook.ts index c0b6aec89..254655fe7 100644 --- a/packages/create-app/templates/default-app/packages/client/src/utils/hooks/FootprintServiceHook.ts +++ b/packages/create-app/templates/default-app/packages/client/src/utils/hooks/FootprintServiceHook.ts @@ -123,7 +123,7 @@ const concatenateResults = (estimates, newEstimates) => { ) if (newDateExists) { - const elementIndex = updatedEstimates.find((estimate) => + const elementIndex = updatedEstimates.findIndex((estimate) => moment .utc(estimate.timestamp) .isSame(moment.utc(newEstimate.timestamp)), From cd3da6031a8b96d845842ed51948512d39eee964 Mon Sep 17 00:00:00 2001 From: Matthew Griffin Date: Thu, 3 Aug 2023 08:13:26 +0100 Subject: [PATCH 206/251] [1193] Add changeset --- .changeset/clean-icons-heal.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changeset/clean-icons-heal.md diff --git a/.changeset/clean-icons-heal.md b/.changeset/clean-icons-heal.md new file mode 100644 index 000000000..bf621803f --- /dev/null +++ b/.changeset/clean-icons-heal.md @@ -0,0 +1,6 @@ +--- +'@cloud-carbon-footprint/client': patch +--- + +Fix issue with concatentation of data from multiple footprint requests +For changes to create-app templates, please refer to [this commit](https://github.com/mgriffin-scottlogic/cloud-carbon-footprint/commit/ff31d78ce2655469dcea2bc2afca226a4dbabb7f). \ No newline at end of file From 3ce7790bf664b3cef325fc100d5eb26ef8180342 Mon Sep 17 00:00:00 2001 From: Matthew Griffin Date: Fri, 4 Aug 2023 08:45:41 +0100 Subject: [PATCH 207/251] [1193] Fix changeset description --- .changeset/clean-icons-heal.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.changeset/clean-icons-heal.md b/.changeset/clean-icons-heal.md index bf621803f..396bbc60f 100644 --- a/.changeset/clean-icons-heal.md +++ b/.changeset/clean-icons-heal.md @@ -1,6 +1,7 @@ --- '@cloud-carbon-footprint/client': patch +'@cloud-carbon-footprint/create-app': patch --- Fix issue with concatentation of data from multiple footprint requests -For changes to create-app templates, please refer to [this commit](https://github.com/mgriffin-scottlogic/cloud-carbon-footprint/commit/ff31d78ce2655469dcea2bc2afca226a4dbabb7f). \ No newline at end of file +For changes to create-app templates, please refer to [this commit](https://github.com/cloud-carbon-footprint/cloud-carbon-footprint/pull/1194/commits/65f76245d4947cd0bb1b7e4a5b761868dd9c6da0). \ No newline at end of file From df80cae5a3f82cbdc6e5d737b5715ca66d8348b7 Mon Sep 17 00:00:00 2001 From: Hank Date: Wed, 2 Aug 2023 11:57:57 +0800 Subject: [PATCH 208/251] Add unsupported usage unit of Azure - 1 GB Hour/1 GB Second --- packages/azure/src/lib/ConsumptionManagement.ts | 9 ++++++++- packages/azure/src/lib/ConsumptionTypes.ts | 2 ++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/packages/azure/src/lib/ConsumptionManagement.ts b/packages/azure/src/lib/ConsumptionManagement.ts index eb9cb0517..280bb0605 100644 --- a/packages/azure/src/lib/ConsumptionManagement.ts +++ b/packages/azure/src/lib/ConsumptionManagement.ts @@ -495,7 +495,9 @@ export default class ConsumptionManagementService { powerUsageEffectiveness, emissionsFactors, ) + case MEMORY_USAGE_UNITS.GB_SECOND_1: case MEMORY_USAGE_UNITS.GB_SECONDS_50000: + case MEMORY_USAGE_UNITS.GB_HOUR_1: case MEMORY_USAGE_UNITS.GB_HOURS_1000: return this.getMemoryFootprintEstimate( consumptionDetailRow, @@ -749,8 +751,13 @@ export default class ConsumptionManagementService { private getUsageAmountInGigabyteHours( consumptionDetailRow: ConsumptionDetailRow, ): number { - if (consumptionDetailRow.usageUnit === MEMORY_USAGE_UNITS.GB_SECONDS_50000) + if ( + consumptionDetailRow.usageUnit === MEMORY_USAGE_UNITS.GB_SECONDS_50000 || + consumptionDetailRow.usageUnit === MEMORY_USAGE_UNITS.GB_SECOND_1 + ) return consumptionDetailRow.usageAmount / 3600 + if (consumptionDetailRow.usageUnit === MEMORY_USAGE_UNITS.GB_HOUR_1) + return consumptionDetailRow.usageAmount return this.getGigabyteHoursFromInstanceTypeAndProcessors( consumptionDetailRow.usageType, consumptionDetailRow.usageAmount, diff --git a/packages/azure/src/lib/ConsumptionTypes.ts b/packages/azure/src/lib/ConsumptionTypes.ts index df1c2f2ac..c42953c15 100644 --- a/packages/azure/src/lib/ConsumptionTypes.ts +++ b/packages/azure/src/lib/ConsumptionTypes.ts @@ -83,7 +83,9 @@ export enum NETWORKING_USAGE_UNITS { } export enum MEMORY_USAGE_UNITS { + GB_SECOND_1 = '1 GB Second', GB_SECONDS_50000 = '50000 GB Seconds', + GB_HOUR_1 = '1 GB Hour', GB_HOURS_1000 = '1000 GB Hours', } From 78f3dec4636f7e23249a8f4ba7c82e4138c0f4cb Mon Sep 17 00:00:00 2001 From: Hank Date: Sun, 6 Aug 2023 14:41:16 +0800 Subject: [PATCH 209/251] Add the test cases for returning estimates of Azure Memory usage unit - 1 GB Hour/1 GB Second --- .../__tests__/ConsumptionManagement.test.ts | 36 ++++++++++++++++++ .../consumptionManagement.fixtures.ts | 38 +++++++++++++++++++ 2 files changed, 74 insertions(+) diff --git a/packages/azure/src/__tests__/ConsumptionManagement.test.ts b/packages/azure/src/__tests__/ConsumptionManagement.test.ts index 107c77baf..f5efd0388 100644 --- a/packages/azure/src/__tests__/ConsumptionManagement.test.ts +++ b/packages/azure/src/__tests__/ConsumptionManagement.test.ts @@ -540,6 +540,42 @@ describe('Azure Consumption Management Service', () => { periodEndDate: new Date('2020-11-02T23:59:59.000Z'), periodStartDate: new Date('2020-11-02T00:00:00.000Z'), }, + { + timestamp: new Date('2020-11-04'), + serviceEstimates: [ + { + accountId: subscriptionId, + accountName: subscriptionName, + cloudProvider: 'AZURE', + co2e: 5.878553215277167e-10, + cost: 10, + region: 'EastUS', + serviceName: 'Functions', + usesAverageCPUConstant: false, + kilowattHours: 0.0000015507871166666667, + tags: { + resourceGroup: 'test-resource-group', + }, + }, + { + accountId: subscriptionId, + accountName: subscriptionName, + cloudProvider: 'AZURE', + co2e: 0.0005042891453706233, + cost: 7, + region: 'CentralUS', + serviceName: 'Container Instances', + usesAverageCPUConstant: false, + kilowattHours: 1.1830719368513218, + tags: { + resourceGroup: 'test-resource-group', + }, + }, + ], + groupBy: grouping, + periodEndDate: new Date('2020-11-04T23:59:59.000Z'), + periodStartDate: new Date('2020-11-04T00:00:00.000Z'), + }, ] expect(result).toEqual(expectedResult) }) diff --git a/packages/azure/src/__tests__/fixtures/consumptionManagement.fixtures.ts b/packages/azure/src/__tests__/fixtures/consumptionManagement.fixtures.ts index 6c9f6fc82..5a674274f 100644 --- a/packages/azure/src/__tests__/fixtures/consumptionManagement.fixtures.ts +++ b/packages/azure/src/__tests__/fixtures/consumptionManagement.fixtures.ts @@ -562,6 +562,44 @@ export const mockConsumptionManagementResponseFive: UsageDetailResult[] = [ resourceLocation: 'ukwest', resourceGroup: 'test-resource-group', }, + { + id: 'test-subscription-id', + kind: 'legacy', + name: 'name', + type: 'type', + tags: {}, + + date: new Date('2020-11-04'), + quantity: 12.0185, + cost: 10, + meterDetails: { + meterName: 'Standard Execution Time', + unitOfMeasure: '1 GB Second', + meterCategory: 'Functions', + }, + subscriptionName: 'test-subscription', + resourceLocation: 'EastUS', + resourceGroup: 'test-resource-group', + }, + { + id: 'test-subscription-id', + kind: 'legacy', + name: 'name', + type: 'type', + tags: {}, + + date: new Date('2020-11-04'), + quantity: 2546.8697512514464, + cost: 7, + meterDetails: { + meterName: 'Standard Memory Duration', + unitOfMeasure: '1 GB Hour', + meterCategory: 'Container Instances', + }, + subscriptionName: 'test-subscription', + resourceLocation: 'CentralUS', + resourceGroup: 'test-resource-group', + }, ] export const mockConsumptionManagementResponseSix: UsageDetailResult[] = [ From 381763797e31ee6e5bf37415145cb09414301719 Mon Sep 17 00:00:00 2001 From: Arik Smith <--global> Date: Wed, 9 Aug 2023 15:08:40 -0400 Subject: [PATCH 210/251] updates package description for aliyun --- packages/ali/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ali/package.json b/packages/ali/package.json index 9f211ff78..282d3f450 100644 --- a/packages/ali/package.json +++ b/packages/ali/package.json @@ -2,7 +2,7 @@ "name": "@cloud-carbon-footprint/ali", "version": "0.1.0", "license": "Apache-2.0", - "description": "The core logic to get cloud usage data and estimate energy and carbon emissions from Amazon Web Services.", + "description": "The core logic to get cloud usage data and estimate energy and carbon emissions from Alibaba Cloud (Aliyun).", "main": "src/index.ts", "types": "src/index.ts", "publishConfig": { From fc4a1c9323ea2344becd530161653e95b8b1fbee Mon Sep 17 00:00:00 2001 From: Arik Smith <--global> Date: Wed, 9 Aug 2023 15:11:03 -0400 Subject: [PATCH 211/251] changeset: Fixes package.json description for initial release --- .changeset/smooth-geckos-camp.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/smooth-geckos-camp.md diff --git a/.changeset/smooth-geckos-camp.md b/.changeset/smooth-geckos-camp.md new file mode 100644 index 000000000..2b8499e7c --- /dev/null +++ b/.changeset/smooth-geckos-camp.md @@ -0,0 +1,5 @@ +--- +'@cloud-carbon-footprint/ali': patch +--- + +Fixes package.json description for initial release From 8b52471ededa18f8cbca65dc3766239f3ada9adc Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 9 Aug 2023 19:13:27 +0000 Subject: [PATCH 212/251] Version Packages --- .changeset/clean-icons-heal.md | 7 ------- .changeset/fuzzy-insects-develop.md | 5 ----- .changeset/little-sheep-fry.md | 5 ----- .changeset/lucky-olives-suffer.md | 5 ----- .changeset/lucky-walls-deny.md | 5 ----- .changeset/smooth-geckos-camp.md | 5 ----- .changeset/ten-pets-buy.md | 5 ----- .changeset/thick-tomatoes-hug.md | 5 ----- .changeset/unlucky-ties-chew.md | 6 ------ packages/ali/CHANGELOG.md | 7 +++++++ packages/ali/package.json | 2 +- packages/aws/CHANGELOG.md | 6 ++++++ packages/aws/package.json | 2 +- packages/azure/CHANGELOG.md | 6 ++++++ packages/azure/package.json | 2 +- packages/cli/CHANGELOG.md | 6 ++++++ packages/cli/package.json | 2 +- packages/client/CHANGELOG.md | 11 +++++++++++ packages/client/package.json | 2 +- packages/create-app/CHANGELOG.md | 11 +++++++++++ packages/create-app/package.json | 2 +- packages/integration-tests/CHANGELOG.md | 14 ++++++++++++++ packages/integration-tests/package.json | 2 +- 23 files changed, 68 insertions(+), 55 deletions(-) delete mode 100644 .changeset/clean-icons-heal.md delete mode 100644 .changeset/fuzzy-insects-develop.md delete mode 100644 .changeset/little-sheep-fry.md delete mode 100644 .changeset/lucky-olives-suffer.md delete mode 100644 .changeset/lucky-walls-deny.md delete mode 100644 .changeset/smooth-geckos-camp.md delete mode 100644 .changeset/ten-pets-buy.md delete mode 100644 .changeset/thick-tomatoes-hug.md delete mode 100644 .changeset/unlucky-ties-chew.md diff --git a/.changeset/clean-icons-heal.md b/.changeset/clean-icons-heal.md deleted file mode 100644 index 396bbc60f..000000000 --- a/.changeset/clean-icons-heal.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -'@cloud-carbon-footprint/client': patch -'@cloud-carbon-footprint/create-app': patch ---- - -Fix issue with concatentation of data from multiple footprint requests -For changes to create-app templates, please refer to [this commit](https://github.com/cloud-carbon-footprint/cloud-carbon-footprint/pull/1194/commits/65f76245d4947cd0bb1b7e4a5b761868dd9c6da0). \ No newline at end of file diff --git a/.changeset/fuzzy-insects-develop.md b/.changeset/fuzzy-insects-develop.md deleted file mode 100644 index 6b2edbff0..000000000 --- a/.changeset/fuzzy-insects-develop.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@cloud-carbon-footprint/aws': patch ---- - -Fixes Athena query column error for accounts without EC2 hours/usage diff --git a/.changeset/little-sheep-fry.md b/.changeset/little-sheep-fry.md deleted file mode 100644 index fe96c22cc..000000000 --- a/.changeset/little-sheep-fry.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@cloud-carbon-footprint/integration-tests': major ---- - -Migrates to Testcafe version 3, and reduces test concurrency diff --git a/.changeset/lucky-olives-suffer.md b/.changeset/lucky-olives-suffer.md deleted file mode 100644 index b8ccb4fdd..000000000 --- a/.changeset/lucky-olives-suffer.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@cloud-carbon-footprint/azure': patch ---- - -Adds aditional aliases for Azure regions diff --git a/.changeset/lucky-walls-deny.md b/.changeset/lucky-walls-deny.md deleted file mode 100644 index b7960db76..000000000 --- a/.changeset/lucky-walls-deny.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@cloud-carbon-footprint/integration-tests': minor ---- - -Integration tests now disable forecast validation for testing, along with improved assertions diff --git a/.changeset/smooth-geckos-camp.md b/.changeset/smooth-geckos-camp.md deleted file mode 100644 index 2b8499e7c..000000000 --- a/.changeset/smooth-geckos-camp.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@cloud-carbon-footprint/ali': patch ---- - -Fixes package.json description for initial release diff --git a/.changeset/ten-pets-buy.md b/.changeset/ten-pets-buy.md deleted file mode 100644 index b473e229c..000000000 --- a/.changeset/ten-pets-buy.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@cloud-carbon-footprint/integration-tests': patch ---- - -Fixes failing recommendations test diff --git a/.changeset/thick-tomatoes-hug.md b/.changeset/thick-tomatoes-hug.md deleted file mode 100644 index 6aa132faa..000000000 --- a/.changeset/thick-tomatoes-hug.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@cloud-carbon-footprint/cli': patch ---- - -Changes seed-cache-file request splitting frequency to be based on groupBy parameter diff --git a/.changeset/unlucky-ties-chew.md b/.changeset/unlucky-ties-chew.md deleted file mode 100644 index 1f0ccebe5..000000000 --- a/.changeset/unlucky-ties-chew.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@cloud-carbon-footprint/client': minor -'@cloud-carbon-footprint/create-app': minor ---- - -Adds config for disabling forecast date validation diff --git a/packages/ali/CHANGELOG.md b/packages/ali/CHANGELOG.md index e69de29bb..23e91cab5 100644 --- a/packages/ali/CHANGELOG.md +++ b/packages/ali/CHANGELOG.md @@ -0,0 +1,7 @@ +# @cloud-carbon-footprint/ali + +## 0.1.1 + +### Patch Changes + +- fc4a1c93: Fixes package.json description for initial release diff --git a/packages/ali/package.json b/packages/ali/package.json index 282d3f450..2cc863942 100644 --- a/packages/ali/package.json +++ b/packages/ali/package.json @@ -1,6 +1,6 @@ { "name": "@cloud-carbon-footprint/ali", - "version": "0.1.0", + "version": "0.1.1", "license": "Apache-2.0", "description": "The core logic to get cloud usage data and estimate energy and carbon emissions from Alibaba Cloud (Aliyun).", "main": "src/index.ts", diff --git a/packages/aws/CHANGELOG.md b/packages/aws/CHANGELOG.md index e33cc06f8..dd7141047 100644 --- a/packages/aws/CHANGELOG.md +++ b/packages/aws/CHANGELOG.md @@ -1,5 +1,11 @@ # @cloud-carbon-footprint/aws +## 0.14.5 + +### Patch Changes + +- 6bf5745d: Fixes Athena query column error for accounts without EC2 hours/usage + ## 0.14.4 ### Patch Changes diff --git a/packages/aws/package.json b/packages/aws/package.json index fea9c2816..065f36064 100644 --- a/packages/aws/package.json +++ b/packages/aws/package.json @@ -1,6 +1,6 @@ { "name": "@cloud-carbon-footprint/aws", - "version": "0.14.4", + "version": "0.14.5", "license": "Apache-2.0", "description": "The core logic to get cloud usage data and estimate energy and carbon emissions from Amazon Web Services.", "main": "src/index.ts", diff --git a/packages/azure/CHANGELOG.md b/packages/azure/CHANGELOG.md index 5c8a03284..e7cc16481 100644 --- a/packages/azure/CHANGELOG.md +++ b/packages/azure/CHANGELOG.md @@ -1,5 +1,11 @@ # @cloud-carbon-footprint/azure +## 1.4.1 + +### Patch Changes + +- c515ba60: Adds aditional aliases for Azure regions + ## 1.4.0 ### Minor Changes diff --git a/packages/azure/package.json b/packages/azure/package.json index 845eedc75..553f25bcb 100644 --- a/packages/azure/package.json +++ b/packages/azure/package.json @@ -1,6 +1,6 @@ { "name": "@cloud-carbon-footprint/azure", - "version": "1.4.0", + "version": "1.4.1", "license": "Apache-2.0", "description": "The core logic to get cloud usage data and estimate energy and carbon emissions from Microsoft Azure.", "main": "src/index.ts", diff --git a/packages/cli/CHANGELOG.md b/packages/cli/CHANGELOG.md index 01be20a6f..5048727d8 100644 --- a/packages/cli/CHANGELOG.md +++ b/packages/cli/CHANGELOG.md @@ -1,5 +1,11 @@ # @cloud-carbon-footprint/cli +## 1.11.1 + +### Patch Changes + +- 056c0c8b: Changes seed-cache-file request splitting frequency to be based on groupBy parameter + ## 1.11.0 ### Minor Changes diff --git a/packages/cli/package.json b/packages/cli/package.json index dd7955fe3..78c321c8a 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@cloud-carbon-footprint/cli", - "version": "1.11.0", + "version": "1.11.1", "license": "Apache-2.0", "description": "Command Line Interface as an entrypoint to get cloud energy and carbon emissions.", "main": "src/index.ts", diff --git a/packages/client/CHANGELOG.md b/packages/client/CHANGELOG.md index ae1a875b8..1f4245392 100644 --- a/packages/client/CHANGELOG.md +++ b/packages/client/CHANGELOG.md @@ -1,5 +1,16 @@ # @cloud-carbon-footprint/client +## 4.2.0 + +### Minor Changes + +- 6bf5745d: Adds config for disabling forecast date validation + +### Patch Changes + +- cd3da603: Fix issue with concatentation of data from multiple footprint requests + For changes to create-app templates, please refer to [this commit](https://github.com/cloud-carbon-footprint/cloud-carbon-footprint/pull/1194/commits/65f76245d4947cd0bb1b7e4a5b761868dd9c6da0). + ## 4.1.3 ### Patch Changes diff --git a/packages/client/package.json b/packages/client/package.json index ea3a6d611..43dc2c6e6 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -1,6 +1,6 @@ { "name": "@cloud-carbon-footprint/client", - "version": "4.1.3", + "version": "4.2.0", "license": "Apache-2.0", "description": "The front-end dashboard for Cloud Carbon Footprint.", "main": "src/index.tsx", diff --git a/packages/create-app/CHANGELOG.md b/packages/create-app/CHANGELOG.md index 3867bf774..281c84887 100644 --- a/packages/create-app/CHANGELOG.md +++ b/packages/create-app/CHANGELOG.md @@ -1,5 +1,16 @@ # @cloud-carbon-footprint/create-app +## 2.5.0 + +### Minor Changes + +- 6bf5745d: Adds config for disabling forecast date validation + +### Patch Changes + +- cd3da603: Fix issue with concatentation of data from multiple footprint requests + For changes to create-app templates, please refer to [this commit](https://github.com/cloud-carbon-footprint/cloud-carbon-footprint/pull/1194/commits/65f76245d4947cd0bb1b7e4a5b761868dd9c6da0). + ## 2.4.0 ### Minor Changes diff --git a/packages/create-app/package.json b/packages/create-app/package.json index 78e70dbe0..7bee59de9 100644 --- a/packages/create-app/package.json +++ b/packages/create-app/package.json @@ -1,6 +1,6 @@ { "name": "@cloud-carbon-footprint/create-app", - "version": "2.4.0", + "version": "2.5.0", "license": "Apache-2.0", "description": "Create app package for Cloud Carbon Footprint", "main": "dist/index.js", diff --git a/packages/integration-tests/CHANGELOG.md b/packages/integration-tests/CHANGELOG.md index 07119469f..58099e45c 100644 --- a/packages/integration-tests/CHANGELOG.md +++ b/packages/integration-tests/CHANGELOG.md @@ -1,5 +1,19 @@ # @cloud-carbon-footprint/integration-tests +## 2.0.0 + +### Major Changes + +- 35b0030e: Migrates to Testcafe version 3, and reduces test concurrency + +### Minor Changes + +- 8a8482c8: Integration tests now disable forecast validation for testing, along with improved assertions + +### Patch Changes + +- 6bf5745d: Fixes failing recommendations test + ## 1.1.7 ### Patch Changes diff --git a/packages/integration-tests/package.json b/packages/integration-tests/package.json index 600005c7b..71789ca37 100644 --- a/packages/integration-tests/package.json +++ b/packages/integration-tests/package.json @@ -1,6 +1,6 @@ { "name": "@cloud-carbon-footprint/integration-tests", - "version": "1.1.7", + "version": "2.0.0", "private": true, "description": "Test repository to run integration tests", "scripts": { From 6b98d6aeea3680fc99fc308ddcca10840de24b7e Mon Sep 17 00:00:00 2001 From: Arik Smith <--global> Date: Mon, 14 Aug 2023 15:17:47 -0400 Subject: [PATCH 213/251] fixes assertion of INCLUDE_ESTIMATES config value --- packages/common/src/Config.ts | 17 ++++---------- packages/common/src/__tests__/Config.test.ts | 24 ++++++++++++++++---- 2 files changed, 25 insertions(+), 16 deletions(-) diff --git a/packages/common/src/Config.ts b/packages/common/src/Config.ts index 520fbc9d1..d8ed83332 100644 --- a/packages/common/src/Config.ts +++ b/packages/common/src/Config.ts @@ -65,6 +65,7 @@ export interface CCFConfig { } ALI?: { NAME?: string + INCLUDE_ESTIMATES?: boolean authentication?: { accessKeyId: string accessKeySecret: string @@ -158,10 +159,7 @@ const getEnvVar = (envVar: string): string => { const getConfig = (): CCFConfig => ({ AWS: { - INCLUDE_ESTIMATES: !( - !!process.env.AWS_INCLUDE_ESTIMATES && - process.env.AWS_INCLUDE_ESTIMATES === 'false' - ), + INCLUDE_ESTIMATES: process.env.AWS_INCLUDE_ESTIMATES !== 'false', USE_BILLING_DATA: !!process.env.AWS_USE_BILLING_DATA && process.env.AWS_USE_BILLING_DATA !== 'false', @@ -247,10 +245,7 @@ const getConfig = (): CCFConfig => ({ USE_CARBON_FREE_ENERGY_PERCENTAGE: !!process.env.GCP_USE_CARBON_FREE_ENERGY_PERCENTAGE && process.env.GCP_USE_CARBON_FREE_ENERGY_PERCENTAGE !== 'false', - INCLUDE_ESTIMATES: !( - !!process.env.GCP_INCLUDE_ESTIMATES && - process.env.GCP_INCLUDE_ESTIMATES !== 'false' - ), + INCLUDE_ESTIMATES: process.env.GCP_INCLUDE_ESTIMATES !== 'false', USE_BILLING_DATA: !!process.env.GCP_USE_BILLING_DATA && process.env.GCP_USE_BILLING_DATA !== 'false', @@ -265,10 +260,7 @@ const getConfig = (): CCFConfig => ({ RESOURCE_TAG_NAMES: JSON.parse(getGCPResourceTagNames()), }, AZURE: { - INCLUDE_ESTIMATES: !( - !!process.env.AZURE_INCLUDE_ESTIMATES && - process.env.AZURE_INCLUDE_ESTIMATES !== 'false' - ), + INCLUDE_ESTIMATES: process.env.AZURE_INCLUDE_ESTIMATES !== 'false', USE_BILLING_DATA: !!process.env.AZURE_USE_BILLING_DATA && process.env.AZURE_USE_BILLING_DATA !== 'false', @@ -289,6 +281,7 @@ const getConfig = (): CCFConfig => ({ }, ALI: { NAME: 'AliCloud', + INCLUDE_ESTIMATES: process.env.ALI_INCLUDE_ESTIMATES !== 'false', authentication: { accessKeyId: process.env.ALI_ACCESS_KEY, accessKeySecret: process.env.ALI_ACCESS_SECRET, diff --git a/packages/common/src/__tests__/Config.test.ts b/packages/common/src/__tests__/Config.test.ts index 4a3f7b8a3..81107af14 100644 --- a/packages/common/src/__tests__/Config.test.ts +++ b/packages/common/src/__tests__/Config.test.ts @@ -22,10 +22,7 @@ describe('Config', () => { } it('get environment variable from `process.env`', () => { - const fsSpy = jest.spyOn(fs, 'readFileSync').mockImplementation(() => { - throw new Error() - }) - + const fsSpy = jest.spyOn(fs, 'readFileSync') const billingId = 'ezb1' withEnvironment('AWS_BILLING_ACCOUNT_ID', billingId, () => { @@ -100,4 +97,23 @@ describe('Config', () => { }) }) }) + + describe('INCLUDE_ESTIMATES', () => { + const cloudProviders = ['AWS', 'GCP', 'AZURE', 'ALI'] as const + cloudProviders.forEach((provider) => { + it.each([ + [true, 'true'], + [false, 'false'], + [true, ''], + ])( + `sets ${provider}_INCLUDE_ESTIMATES to %s for INCLUDE_ESTIMATES=%s`, + (expected: boolean, value: any) => { + withEnvironment(`${provider}_INCLUDE_ESTIMATES`, value, () => { + const config = getConfig() + expect(config[provider].INCLUDE_ESTIMATES).toBe(expected) + }) + }, + ) + }) + }) }) From bc06b8618d9283f8a5ee758f4d76333c5a9fe4ba Mon Sep 17 00:00:00 2001 From: Arik Smith <--global> Date: Mon, 14 Aug 2023 15:36:03 -0400 Subject: [PATCH 214/251] changeset: Fixes issue when asserting set values of INCLUDE_ESTIMATES config --- .changeset/fast-dryers-attack.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/fast-dryers-attack.md diff --git a/.changeset/fast-dryers-attack.md b/.changeset/fast-dryers-attack.md new file mode 100644 index 000000000..275b5d48f --- /dev/null +++ b/.changeset/fast-dryers-attack.md @@ -0,0 +1,5 @@ +--- +'@cloud-carbon-footprint/common': patch +--- + +Fixes issue when asserting set values of INCLUDE_ESTIMATES config From 9f976e5d71ebb7ecf72886eeeff2120ba577efa4 Mon Sep 17 00:00:00 2001 From: Matthew Griffin Date: Tue, 15 Aug 2023 13:47:01 +0100 Subject: [PATCH 215/251] [1205] Export buildTagQuery function to allow for individual tests --- .talismanrc | 4 +-- .../src/__tests__/BillingExportTable.test.ts | 17 ++++++++++- packages/gcp/src/lib/BillingExportTable.ts | 28 +++++++++---------- 3 files changed, 32 insertions(+), 17 deletions(-) diff --git a/.talismanrc b/.talismanrc index 1ae7f9e2b..d635b11f8 100644 --- a/.talismanrc +++ b/.talismanrc @@ -374,11 +374,11 @@ fileignoreconfig: - filename: packages/create-app/templates/default-app/packages/client/stub-server/mockData.json checksum: 9073338cab91e48f6b789c52f415d8559926d791d80aeb2954cb2fb842202a66 - filename: packages/gcp/src/__tests__/BillingExportTable.test.ts - checksum: 806ca071f5f496e5bd682f0474543f9b6e6c7f5d687e8db89cf244371393188f + checksum: fde9a9d7644f1e8b7ae5776bff8a3aabc72a6a23e8193bc2a6a9344e45a88f6e - filename: packages/gcp/src/__tests__/GCPAccount.test.ts checksum: 26ef0caf65177a4e013ecff94012824cee58ceed48c09b29f7710712084ab9c4 - filename: packages/gcp/src/lib/BillingExportTable.ts - checksum: df8f16310cd2bf983821666d495f5bf1945584851e1242e37ecdf0a652102113 + checksum: f5b98d241e9b4bd1c690adf5999ce3eaf5d6cc6d7e91cd5e9c29b6f3bc3bb415 - filename: packages/gcp/src/lib/BillingExportTypes.ts checksum: 27e7319ef9204e0c6e575ddf8bf288d2627fb8542f468ddde86439420be8c93d - filename: packages/gcp/src/lib/MachineTypes.ts diff --git a/packages/gcp/src/__tests__/BillingExportTable.test.ts b/packages/gcp/src/__tests__/BillingExportTable.test.ts index 2184c3fa5..daa657025 100644 --- a/packages/gcp/src/__tests__/BillingExportTable.test.ts +++ b/packages/gcp/src/__tests__/BillingExportTable.test.ts @@ -20,7 +20,7 @@ import { } from '@cloud-carbon-footprint/core' import { GCP_CLOUD_CONSTANTS } from '../domain' -import BillingExportTable from '../lib/BillingExportTable' +import BillingExportTable, { buildTagQuery } from '../lib/BillingExportTable' import { mockQueryAppEngineComputeUnknownRegion, mockQueryCloudSpannerKubernetesEngineAndRequestsUsageTypesWithReplicationFactors, @@ -1644,3 +1644,18 @@ describe('GCP BillingExportTable Service', () => { ) }) }) + +describe('Creating Tag queries', () => { + it('returns query parts for a single tag', () => { + const [propertySelections, propertyJoins] = buildTagQuery('tags', [ + 'environment', + ]) + expect(propertySelections).toEqual( + ', STRING_AGG(DISTINCT CONCAT(tags.key, ": ", tags.value), ", ") AS tags', + ) + expect(propertyJoins).toEqual(` +LEFT JOIN + UNNEST(tags) AS tags +ON tags.key = "environment"`) + }) +}) diff --git a/packages/gcp/src/lib/BillingExportTable.ts b/packages/gcp/src/lib/BillingExportTable.ts index 845d035be..5fd8c3d2f 100644 --- a/packages/gcp/src/lib/BillingExportTable.ts +++ b/packages/gcp/src/lib/BillingExportTable.ts @@ -615,16 +615,16 @@ export default class BillingExportTable { const [tags, labels, projectLabels] = this.tagNamesToQueryColumns(tagNames) - const [tagPropertySelections, tagPropertyJoins] = this.buildTagQuery( + const [tagPropertySelections, tagPropertyJoins] = buildTagQuery( 'tags', tags, ) - const [labelPropertySelections, labelPropertyJoins] = this.buildTagQuery( + const [labelPropertySelections, labelPropertyJoins] = buildTagQuery( 'labels', labels, ) const [projectLabelPropertySelections, projectLabelPropertyJoins] = - this.buildTagQuery('projectLabels', projectLabels) + buildTagQuery('projectLabels', projectLabels) const query = `SELECT DATE_TRUNC(DATE(usage_start_time), ${ @@ -742,19 +742,19 @@ export default class BillingExportTable { return parsedTags } +} - private buildTagQuery(columnName: string, keys: string[]): string[] { - let propertySelections = '', - propertyJoins = '' +export const buildTagQuery = (columnName: string, keys: string[]): string[] => { + let propertySelections = '', + propertyJoins = '' - if (keys.length > 0) { - propertySelections = `, STRING_AGG(DISTINCT CONCAT(${columnName}.key, ": ", ${columnName}.value), ", ") AS ${columnName}` + if (keys.length > 0) { + propertySelections = `, STRING_AGG(DISTINCT CONCAT(${columnName}.key, ": ", ${columnName}.value), ", ") AS ${columnName}` - propertyJoins = `\nLEFT JOIN\n UNNEST(${ - columnName === 'projectLabels' ? 'project.label' : columnName - }) AS ${columnName}\n` - propertyJoins += keys.map((tag) => `ON tags.key = "${tag}"`).join(' OR ') - } - return [propertySelections, propertyJoins] + propertyJoins = `\nLEFT JOIN\n UNNEST(${ + columnName === 'projectLabels' ? 'project.label' : columnName + }) AS ${columnName}\n` + propertyJoins += keys.map((tag) => `ON tags.key = "${tag}"`).join(' OR ') } + return [propertySelections, propertyJoins] } From bacb3f80dc5b6198ffefdbe102be3b7c1849e917 Mon Sep 17 00:00:00 2001 From: Matthew Griffin Date: Tue, 15 Aug 2023 14:13:25 +0100 Subject: [PATCH 216/251] [1205] Remove repeated 'ON' from tag query join --- .talismanrc | 4 ++-- .../gcp/src/__tests__/BillingExportTable.test.ts | 14 ++++++++++++++ packages/gcp/src/lib/BillingExportTable.ts | 3 ++- 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/.talismanrc b/.talismanrc index d635b11f8..8b2d9b308 100644 --- a/.talismanrc +++ b/.talismanrc @@ -374,11 +374,11 @@ fileignoreconfig: - filename: packages/create-app/templates/default-app/packages/client/stub-server/mockData.json checksum: 9073338cab91e48f6b789c52f415d8559926d791d80aeb2954cb2fb842202a66 - filename: packages/gcp/src/__tests__/BillingExportTable.test.ts - checksum: fde9a9d7644f1e8b7ae5776bff8a3aabc72a6a23e8193bc2a6a9344e45a88f6e + checksum: 16a80597f84d987a034b728e17edb5996e2c41b36d917193b0ad073d10e99b0e - filename: packages/gcp/src/__tests__/GCPAccount.test.ts checksum: 26ef0caf65177a4e013ecff94012824cee58ceed48c09b29f7710712084ab9c4 - filename: packages/gcp/src/lib/BillingExportTable.ts - checksum: f5b98d241e9b4bd1c690adf5999ce3eaf5d6cc6d7e91cd5e9c29b6f3bc3bb415 + checksum: f6a519b50b44c0dd04e81ac71fff6335c79af681b8942fab11ba8f04de3cf5f8 - filename: packages/gcp/src/lib/BillingExportTypes.ts checksum: 27e7319ef9204e0c6e575ddf8bf288d2627fb8542f468ddde86439420be8c93d - filename: packages/gcp/src/lib/MachineTypes.ts diff --git a/packages/gcp/src/__tests__/BillingExportTable.test.ts b/packages/gcp/src/__tests__/BillingExportTable.test.ts index daa657025..927762119 100644 --- a/packages/gcp/src/__tests__/BillingExportTable.test.ts +++ b/packages/gcp/src/__tests__/BillingExportTable.test.ts @@ -1658,4 +1658,18 @@ LEFT JOIN UNNEST(tags) AS tags ON tags.key = "environment"`) }) + + it('returns query parts for multiple tags', () => { + const [propertySelections, propertyJoins] = buildTagQuery('tags', [ + 'environment', + 'project', + ]) + expect(propertySelections).toEqual( + ', STRING_AGG(DISTINCT CONCAT(tags.key, ": ", tags.value), ", ") AS tags', + ) + expect(propertyJoins).toEqual(` +LEFT JOIN + UNNEST(tags) AS tags +ON tags.key = "environment" OR tags.key = "project"`) + }) }) diff --git a/packages/gcp/src/lib/BillingExportTable.ts b/packages/gcp/src/lib/BillingExportTable.ts index 5fd8c3d2f..9fbcc2ba2 100644 --- a/packages/gcp/src/lib/BillingExportTable.ts +++ b/packages/gcp/src/lib/BillingExportTable.ts @@ -754,7 +754,8 @@ export const buildTagQuery = (columnName: string, keys: string[]): string[] => { propertyJoins = `\nLEFT JOIN\n UNNEST(${ columnName === 'projectLabels' ? 'project.label' : columnName }) AS ${columnName}\n` - propertyJoins += keys.map((tag) => `ON tags.key = "${tag}"`).join(' OR ') + const keyJoins = keys.map((tag) => `tags.key = "${tag}"`).join(' OR ') + propertyJoins += `ON ${keyJoins}` } return [propertySelections, propertyJoins] } From 601e950a34469358b9d2c5845e0870f1f44d1ea6 Mon Sep 17 00:00:00 2001 From: Matthew Griffin Date: Tue, 15 Aug 2023 15:32:30 +0100 Subject: [PATCH 217/251] [1205] Ensure that the key joins reference the correct tag type --- .talismanrc | 4 ++-- .../gcp/src/__tests__/BillingExportTable.test.ts | 13 +++++++++++++ packages/gcp/src/lib/BillingExportTable.ts | 4 +++- 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/.talismanrc b/.talismanrc index 8b2d9b308..805c64ab2 100644 --- a/.talismanrc +++ b/.talismanrc @@ -374,11 +374,11 @@ fileignoreconfig: - filename: packages/create-app/templates/default-app/packages/client/stub-server/mockData.json checksum: 9073338cab91e48f6b789c52f415d8559926d791d80aeb2954cb2fb842202a66 - filename: packages/gcp/src/__tests__/BillingExportTable.test.ts - checksum: 16a80597f84d987a034b728e17edb5996e2c41b36d917193b0ad073d10e99b0e + checksum: c56cbb44416f5355d2fdca848e388b6aa027858be6f3fbc959aa1074049ac2be - filename: packages/gcp/src/__tests__/GCPAccount.test.ts checksum: 26ef0caf65177a4e013ecff94012824cee58ceed48c09b29f7710712084ab9c4 - filename: packages/gcp/src/lib/BillingExportTable.ts - checksum: f6a519b50b44c0dd04e81ac71fff6335c79af681b8942fab11ba8f04de3cf5f8 + checksum: 96f2b97dfa7265469d3a6958aac504414c2f298bd214364d925b3c0b65efa2a7 - filename: packages/gcp/src/lib/BillingExportTypes.ts checksum: 27e7319ef9204e0c6e575ddf8bf288d2627fb8542f468ddde86439420be8c93d - filename: packages/gcp/src/lib/MachineTypes.ts diff --git a/packages/gcp/src/__tests__/BillingExportTable.test.ts b/packages/gcp/src/__tests__/BillingExportTable.test.ts index 927762119..e776e8213 100644 --- a/packages/gcp/src/__tests__/BillingExportTable.test.ts +++ b/packages/gcp/src/__tests__/BillingExportTable.test.ts @@ -1672,4 +1672,17 @@ LEFT JOIN UNNEST(tags) AS tags ON tags.key = "environment" OR tags.key = "project"`) }) + + it('returns query parts for a single label', () => { + const [propertySelections, propertyJoins] = buildTagQuery('labels', [ + 'environment', + ]) + expect(propertySelections).toEqual( + ', STRING_AGG(DISTINCT CONCAT(labels.key, ": ", labels.value), ", ") AS labels', + ) + expect(propertyJoins).toEqual(` +LEFT JOIN + UNNEST(labels) AS labels +ON labels.key = "environment"`) + }) }) diff --git a/packages/gcp/src/lib/BillingExportTable.ts b/packages/gcp/src/lib/BillingExportTable.ts index 9fbcc2ba2..44d5ddef4 100644 --- a/packages/gcp/src/lib/BillingExportTable.ts +++ b/packages/gcp/src/lib/BillingExportTable.ts @@ -754,7 +754,9 @@ export const buildTagQuery = (columnName: string, keys: string[]): string[] => { propertyJoins = `\nLEFT JOIN\n UNNEST(${ columnName === 'projectLabels' ? 'project.label' : columnName }) AS ${columnName}\n` - const keyJoins = keys.map((tag) => `tags.key = "${tag}"`).join(' OR ') + const keyJoins = keys + .map((tag) => `${columnName}.key = "${tag}"`) + .join(' OR ') propertyJoins += `ON ${keyJoins}` } return [propertySelections, propertyJoins] From 72619a25d6b0fefadaf4b8ad680f0172012f0d9f Mon Sep 17 00:00:00 2001 From: Matthew Griffin Date: Tue, 15 Aug 2023 16:01:33 +0100 Subject: [PATCH 218/251] [1205] Ensure that project labels use correct field name --- .talismanrc | 2 +- .../gcp/src/__tests__/BillingExportTable.test.ts | 13 +++++++++++++ packages/gcp/src/lib/BillingExportTable.ts | 2 +- 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/.talismanrc b/.talismanrc index 805c64ab2..336fdb727 100644 --- a/.talismanrc +++ b/.talismanrc @@ -374,7 +374,7 @@ fileignoreconfig: - filename: packages/create-app/templates/default-app/packages/client/stub-server/mockData.json checksum: 9073338cab91e48f6b789c52f415d8559926d791d80aeb2954cb2fb842202a66 - filename: packages/gcp/src/__tests__/BillingExportTable.test.ts - checksum: c56cbb44416f5355d2fdca848e388b6aa027858be6f3fbc959aa1074049ac2be + checksum: 50397c9013f12b8b9cc457ffdeee4e5796edf2175dccc2c3aef7c7016c6fb144 - filename: packages/gcp/src/__tests__/GCPAccount.test.ts checksum: 26ef0caf65177a4e013ecff94012824cee58ceed48c09b29f7710712084ab9c4 - filename: packages/gcp/src/lib/BillingExportTable.ts diff --git a/packages/gcp/src/__tests__/BillingExportTable.test.ts b/packages/gcp/src/__tests__/BillingExportTable.test.ts index e776e8213..7f2fd7d74 100644 --- a/packages/gcp/src/__tests__/BillingExportTable.test.ts +++ b/packages/gcp/src/__tests__/BillingExportTable.test.ts @@ -1685,4 +1685,17 @@ LEFT JOIN UNNEST(labels) AS labels ON labels.key = "environment"`) }) + + it('returns query parts for project labels', () => { + const [propertySelections, propertyJoins] = buildTagQuery('projectLabels', [ + 'environment', + ]) + expect(propertySelections).toEqual( + ', STRING_AGG(DISTINCT CONCAT(projectLabels.key, ": ", projectLabels.value), ", ") AS projectLabels', + ) + expect(propertyJoins).toEqual(` +LEFT JOIN + UNNEST(project.labels) AS projectLabels +ON projectLabels.key = "environment"`) + }) }) diff --git a/packages/gcp/src/lib/BillingExportTable.ts b/packages/gcp/src/lib/BillingExportTable.ts index 44d5ddef4..14d2980b3 100644 --- a/packages/gcp/src/lib/BillingExportTable.ts +++ b/packages/gcp/src/lib/BillingExportTable.ts @@ -752,7 +752,7 @@ export const buildTagQuery = (columnName: string, keys: string[]): string[] => { propertySelections = `, STRING_AGG(DISTINCT CONCAT(${columnName}.key, ": ", ${columnName}.value), ", ") AS ${columnName}` propertyJoins = `\nLEFT JOIN\n UNNEST(${ - columnName === 'projectLabels' ? 'project.label' : columnName + columnName === 'projectLabels' ? 'project.labels' : columnName }) AS ${columnName}\n` const keyJoins = keys .map((tag) => `${columnName}.key = "${tag}"`) From ff955bd41cbf38e6c88484d018dfeb7a9c7311e9 Mon Sep 17 00:00:00 2001 From: Matthew Griffin Date: Tue, 15 Aug 2023 16:18:56 +0100 Subject: [PATCH 219/251] [1205] Use query columns in the correct order --- packages/gcp/src/lib/BillingExportTable.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/gcp/src/lib/BillingExportTable.ts b/packages/gcp/src/lib/BillingExportTable.ts index 14d2980b3..f95b3e686 100644 --- a/packages/gcp/src/lib/BillingExportTable.ts +++ b/packages/gcp/src/lib/BillingExportTable.ts @@ -613,7 +613,7 @@ export default class BillingExportTable { ) const endDate = new Date(moment.utc(end).endOf('day') as unknown as Date) - const [tags, labels, projectLabels] = this.tagNamesToQueryColumns(tagNames) + const [tags, projectLabels, labels] = this.tagNamesToQueryColumns(tagNames) const [tagPropertySelections, tagPropertyJoins] = buildTagQuery( 'tags', From 53812d8d2eea3c1cc9b8a9e7d4afd433d00ae6ed Mon Sep 17 00:00:00 2001 From: Matthew Griffin Date: Tue, 15 Aug 2023 16:21:40 +0100 Subject: [PATCH 220/251] [1205] Add changeset --- .changeset/rude-planes-sniff.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/rude-planes-sniff.md diff --git a/.changeset/rude-planes-sniff.md b/.changeset/rude-planes-sniff.md new file mode 100644 index 000000000..91d60ece6 --- /dev/null +++ b/.changeset/rude-planes-sniff.md @@ -0,0 +1,5 @@ +--- +'@cloud-carbon-footprint/gcp': patch +--- + +Fix syntax errors in GCP Queries when using tags/labels From 8ef806a5ba3919ec93fc360231ca582bba433b50 Mon Sep 17 00:00:00 2001 From: Arik Smith Date: Wed, 23 Aug 2023 17:30:37 -0400 Subject: [PATCH 221/251] [#1213] re-initializes blog and updates docusaurus --- microsite/blog/2019-05-30-welcome.md | 13 + microsite/blog/2023-08-23-ccf-on-vm.md | 11 + microsite/blog/authors.yml | 6 + microsite/docusaurus.config.js | 16 +- microsite/yarn.lock | 371 +++++++++++++------------ 5 files changed, 226 insertions(+), 191 deletions(-) create mode 100644 microsite/blog/2019-05-30-welcome.md create mode 100644 microsite/blog/2023-08-23-ccf-on-vm.md create mode 100644 microsite/blog/authors.yml diff --git a/microsite/blog/2019-05-30-welcome.md b/microsite/blog/2019-05-30-welcome.md new file mode 100644 index 000000000..84c8771b4 --- /dev/null +++ b/microsite/blog/2019-05-30-welcome.md @@ -0,0 +1,13 @@ +--- +slug: welcome +title: Welcome +author: Yangshun Tay +author_title: Front End Engineer @ Facebook +author_url: https://github.com/yangshun +author_image_url: https://avatars0.githubusercontent.com/u/1315101?s=400&v=4 +tags: [facebook, hello, docusaurus] +--- + +Blog features are powered by the blog plugin. Simply add files to the `blog` directory. It supports tags as well! + +Delete the whole directory if you don't want the blog features. As simple as that! \ No newline at end of file diff --git a/microsite/blog/2023-08-23-ccf-on-vm.md b/microsite/blog/2023-08-23-ccf-on-vm.md new file mode 100644 index 000000000..26a0c3eb1 --- /dev/null +++ b/microsite/blog/2023-08-23-ccf-on-vm.md @@ -0,0 +1,11 @@ +--- +slug: ccf-on-vm +title: Running CCF in an AWS EC2 Instance +authors: asmith +tags: [thoughtworks, deployment] +--- + +Are you looking to run Cloud Carbon Footprint (CCF) on an AWS EC2 instance? Look no further! This page provides step-by-step instructions on how to launch and connect to the instance, configure Node on the instance, and set up the CCF app. Keep reading to learn how to easily run CCF on your AWS EC2 instance. + + +End of preview. Rest of the content will go here. \ No newline at end of file diff --git a/microsite/blog/authors.yml b/microsite/blog/authors.yml new file mode 100644 index 000000000..9282db7fd --- /dev/null +++ b/microsite/blog/authors.yml @@ -0,0 +1,6 @@ +asmith: + name: Arik Smith + title: Senior Software Engineer @ Thoughtworks + url: https://github.com/4upz + image_url: https://github.com/4upz.png + email: arik.m.smith@gmail.com diff --git a/microsite/docusaurus.config.js b/microsite/docusaurus.config.js index 3bb47bfab..89fce1e4a 100644 --- a/microsite/docusaurus.config.js +++ b/microsite/docusaurus.config.js @@ -44,6 +44,11 @@ module.exports = { position: 'left', className: 'navbar__link', }, + { + to: 'blog', + label: 'Blog', + position: 'left', + }, { to: 'https://github.com/cloud-carbon-footprint/cloud-carbon-footprint', label: 'Github', @@ -81,12 +86,11 @@ module.exports = { docs: { sidebarCollapsible: true, }, - // blog: { - // showReadingTime: true, - // // Please change this to your repo. - // editUrl: - // 'https://github.com/facebook/docusaurus/edit/master/website/blog/', - // }, + blog: { + showReadingTime: true, + editUrl: + 'https://github.com/cloud-carbon-footprint/cloud-carbon-footprint', + }, theme: { customCss: require.resolve('./src/css/custom.css'), }, diff --git a/microsite/yarn.lock b/microsite/yarn.lock index 1442b0873..c3ed4a21b 100644 --- a/microsite/yarn.lock +++ b/microsite/yarn.lock @@ -1679,9 +1679,9 @@ __metadata: languageName: node linkType: hard -"@docusaurus/core@npm:2.3.1, @docusaurus/core@npm:latest": - version: 2.3.1 - resolution: "@docusaurus/core@npm:2.3.1" +"@docusaurus/core@npm:2.4.1, @docusaurus/core@npm:latest": + version: 2.4.1 + resolution: "@docusaurus/core@npm:2.4.1" dependencies: "@babel/core": ^7.18.6 "@babel/generator": ^7.18.7 @@ -1693,13 +1693,13 @@ __metadata: "@babel/runtime": ^7.18.6 "@babel/runtime-corejs3": ^7.18.6 "@babel/traverse": ^7.18.8 - "@docusaurus/cssnano-preset": 2.3.1 - "@docusaurus/logger": 2.3.1 - "@docusaurus/mdx-loader": 2.3.1 + "@docusaurus/cssnano-preset": 2.4.1 + "@docusaurus/logger": 2.4.1 + "@docusaurus/mdx-loader": 2.4.1 "@docusaurus/react-loadable": 5.5.2 - "@docusaurus/utils": 2.3.1 - "@docusaurus/utils-common": 2.3.1 - "@docusaurus/utils-validation": 2.3.1 + "@docusaurus/utils": 2.4.1 + "@docusaurus/utils-common": 2.4.1 + "@docusaurus/utils-validation": 2.4.1 "@slorber/static-site-generator-webpack-plugin": ^4.0.7 "@svgr/webpack": ^6.2.1 autoprefixer: ^10.4.7 @@ -1759,40 +1759,40 @@ __metadata: react-dom: ^16.8.4 || ^17.0.0 bin: docusaurus: bin/docusaurus.mjs - checksum: 812aecae45af3f4d02fd16e89517ca9f1ba22821a078aaa890f5797ac7e0cc0c79e7623eb999e885cf7e7652a6ffda8ff7c06dfd85ca29aaab600993c3d9980d + checksum: 40c887ef662f7679d803695d4193268c2c177c6d4e13b43b56cc519322522a1608b4bfc4999f6355be778ca7a0256f0d27ab18a19b352a9da1aed66e2644dc82 languageName: node linkType: hard -"@docusaurus/cssnano-preset@npm:2.3.1": - version: 2.3.1 - resolution: "@docusaurus/cssnano-preset@npm:2.3.1" +"@docusaurus/cssnano-preset@npm:2.4.1": + version: 2.4.1 + resolution: "@docusaurus/cssnano-preset@npm:2.4.1" dependencies: cssnano-preset-advanced: ^5.3.8 postcss: ^8.4.14 postcss-sort-media-queries: ^4.2.1 tslib: ^2.4.0 - checksum: a3d00ce86b16caffde36734bb2f4541d2c0df5e8ab6891a78ad05bccc631a895fecb04c385626ebcb8f905510c28fa6158288585673ae96565532d4ee4b60d4f + checksum: d498345981288af2dcb8650bed3c3361cfe336541a8bda65743fbe8ee5746e81e723ba086e2e6249c3b283f4bc50b5c81cff15b0406969cd610bed345b3804ac languageName: node linkType: hard -"@docusaurus/logger@npm:2.3.1": - version: 2.3.1 - resolution: "@docusaurus/logger@npm:2.3.1" +"@docusaurus/logger@npm:2.4.1": + version: 2.4.1 + resolution: "@docusaurus/logger@npm:2.4.1" dependencies: chalk: ^4.1.2 tslib: ^2.4.0 - checksum: eff5f258aeac9c643431426256e3bc4515074cc3cc754fa643579ba427ba232ecace9a9579ae5af542330b22d7361892a1eaf84526983a0c821c5ca3ee895176 + checksum: be81840f2df477ab633d8ced6fd3a512582e764a48d66b1c12bb20b5d4c717f349e254e33b00b9b53381dbdb24a3e3d0ca9b19511366244b3620fa19cc4c69dc languageName: node linkType: hard -"@docusaurus/mdx-loader@npm:2.3.1": - version: 2.3.1 - resolution: "@docusaurus/mdx-loader@npm:2.3.1" +"@docusaurus/mdx-loader@npm:2.4.1": + version: 2.4.1 + resolution: "@docusaurus/mdx-loader@npm:2.4.1" dependencies: "@babel/parser": ^7.18.8 "@babel/traverse": ^7.18.8 - "@docusaurus/logger": 2.3.1 - "@docusaurus/utils": 2.3.1 + "@docusaurus/logger": 2.4.1 + "@docusaurus/utils": 2.4.1 "@mdx-js/mdx": ^1.6.22 escape-html: ^1.0.3 file-loader: ^6.2.0 @@ -1809,16 +1809,16 @@ __metadata: peerDependencies: react: ^16.8.4 || ^17.0.0 react-dom: ^16.8.4 || ^17.0.0 - checksum: 4a1c9ef0e8506ab4d9cb4714ff7437664e238e0f2878a5eb4a2e082897bbee7ae8d0b61ba9d45ffa820beb5ce75aa0050201db815b00c18fc136aaa4c6411c21 + checksum: cf36bbde228a058869dfd770a85f130035a54e563b957a3cfc3191d06efdcfc272bb51b51e6225a0246b233e5d7d0ca1cb4df4b700b837aa72bbb0c9f6f6f5bd languageName: node linkType: hard -"@docusaurus/module-type-aliases@npm:2.3.1": - version: 2.3.1 - resolution: "@docusaurus/module-type-aliases@npm:2.3.1" +"@docusaurus/module-type-aliases@npm:2.4.1": + version: 2.4.1 + resolution: "@docusaurus/module-type-aliases@npm:2.4.1" dependencies: "@docusaurus/react-loadable": 5.5.2 - "@docusaurus/types": 2.3.1 + "@docusaurus/types": 2.4.1 "@types/history": ^4.7.11 "@types/react": "*" "@types/react-router-config": "*" @@ -1828,21 +1828,21 @@ __metadata: peerDependencies: react: "*" react-dom: "*" - checksum: 74f799f81455dc8ff3e6edf07428996764014c2c7b416e6b5d160af15f00ad3aa1ab75dee5356645ec7f2ea832fb2aca6e9a32b19d64abeb9e3d57c4195f1e50 + checksum: 9e328c7bc5cd40b399550995edbeeea5ce88be7eb75f4c49499e8fd05a8bbabf180dce4d1cae0185721629fc6e0f2e8fc513e3ce846080f9771f7a9bc1c45ba8 languageName: node linkType: hard -"@docusaurus/plugin-content-blog@npm:2.3.1": - version: 2.3.1 - resolution: "@docusaurus/plugin-content-blog@npm:2.3.1" - dependencies: - "@docusaurus/core": 2.3.1 - "@docusaurus/logger": 2.3.1 - "@docusaurus/mdx-loader": 2.3.1 - "@docusaurus/types": 2.3.1 - "@docusaurus/utils": 2.3.1 - "@docusaurus/utils-common": 2.3.1 - "@docusaurus/utils-validation": 2.3.1 +"@docusaurus/plugin-content-blog@npm:2.4.1": + version: 2.4.1 + resolution: "@docusaurus/plugin-content-blog@npm:2.4.1" + dependencies: + "@docusaurus/core": 2.4.1 + "@docusaurus/logger": 2.4.1 + "@docusaurus/mdx-loader": 2.4.1 + "@docusaurus/types": 2.4.1 + "@docusaurus/utils": 2.4.1 + "@docusaurus/utils-common": 2.4.1 + "@docusaurus/utils-validation": 2.4.1 cheerio: ^1.0.0-rc.12 feed: ^4.2.2 fs-extra: ^10.1.0 @@ -1855,21 +1855,21 @@ __metadata: peerDependencies: react: ^16.8.4 || ^17.0.0 react-dom: ^16.8.4 || ^17.0.0 - checksum: abc668ceab15269f57be7f74acbec2e139b4f6b90af8771d246a9036d124b49b0d9fd4890e9566df7a4ba960f2da0316c18741eed1be0646f2b4602465219ddd + checksum: 9d4e543b70d032d7edf0c986c45f36a088db76dc737a24374dcb877177b889fb0a5ed7b0a9c9ebb912523ef23ba26787d0fff59d9032b3e8075bdde9c072956a languageName: node linkType: hard -"@docusaurus/plugin-content-docs@npm:2.3.1": - version: 2.3.1 - resolution: "@docusaurus/plugin-content-docs@npm:2.3.1" - dependencies: - "@docusaurus/core": 2.3.1 - "@docusaurus/logger": 2.3.1 - "@docusaurus/mdx-loader": 2.3.1 - "@docusaurus/module-type-aliases": 2.3.1 - "@docusaurus/types": 2.3.1 - "@docusaurus/utils": 2.3.1 - "@docusaurus/utils-validation": 2.3.1 +"@docusaurus/plugin-content-docs@npm:2.4.1": + version: 2.4.1 + resolution: "@docusaurus/plugin-content-docs@npm:2.4.1" + dependencies: + "@docusaurus/core": 2.4.1 + "@docusaurus/logger": 2.4.1 + "@docusaurus/mdx-loader": 2.4.1 + "@docusaurus/module-type-aliases": 2.4.1 + "@docusaurus/types": 2.4.1 + "@docusaurus/utils": 2.4.1 + "@docusaurus/utils-validation": 2.4.1 "@types/react-router-config": ^5.0.6 combine-promises: ^1.1.0 fs-extra: ^10.1.0 @@ -1882,132 +1882,132 @@ __metadata: peerDependencies: react: ^16.8.4 || ^17.0.0 react-dom: ^16.8.4 || ^17.0.0 - checksum: bcf8d921407d11b497926a1f61a1dc8c96f82fbe5a1959cc106b082e555f8fb6f42cf9262a658acf33d9543e5eb3e778049d91f71e4a2855993dc759c845cf31 + checksum: 028eda178dc81a74c25fd2efddb47e44451af2b268b13d99ef2b60cf13da1443f3bce884fd4a8a7ae92fed8ef747308309074f9524753fd80a40b5252a237e37 languageName: node linkType: hard -"@docusaurus/plugin-content-pages@npm:2.3.1": - version: 2.3.1 - resolution: "@docusaurus/plugin-content-pages@npm:2.3.1" +"@docusaurus/plugin-content-pages@npm:2.4.1": + version: 2.4.1 + resolution: "@docusaurus/plugin-content-pages@npm:2.4.1" dependencies: - "@docusaurus/core": 2.3.1 - "@docusaurus/mdx-loader": 2.3.1 - "@docusaurus/types": 2.3.1 - "@docusaurus/utils": 2.3.1 - "@docusaurus/utils-validation": 2.3.1 + "@docusaurus/core": 2.4.1 + "@docusaurus/mdx-loader": 2.4.1 + "@docusaurus/types": 2.4.1 + "@docusaurus/utils": 2.4.1 + "@docusaurus/utils-validation": 2.4.1 fs-extra: ^10.1.0 tslib: ^2.4.0 webpack: ^5.73.0 peerDependencies: react: ^16.8.4 || ^17.0.0 react-dom: ^16.8.4 || ^17.0.0 - checksum: 8543050ed7330f54a28c0daeef11662eed3f3a08a6d0015b1a32db3d5e9ec46f0c6a8a5a4cb3e871ce953074d60424cc418b7ffa280695294626855a7a1a146a + checksum: 6af4eb7c064ed90158ad584eb64593473940b1880034a65fbcfde36116d6702b882bb9b0340141a5a48e67b0f84c03b8202b94171f5924d9f0c279cb68959a47 languageName: node linkType: hard -"@docusaurus/plugin-debug@npm:2.3.1": - version: 2.3.1 - resolution: "@docusaurus/plugin-debug@npm:2.3.1" +"@docusaurus/plugin-debug@npm:2.4.1": + version: 2.4.1 + resolution: "@docusaurus/plugin-debug@npm:2.4.1" dependencies: - "@docusaurus/core": 2.3.1 - "@docusaurus/types": 2.3.1 - "@docusaurus/utils": 2.3.1 + "@docusaurus/core": 2.4.1 + "@docusaurus/types": 2.4.1 + "@docusaurus/utils": 2.4.1 fs-extra: ^10.1.0 react-json-view: ^1.21.3 tslib: ^2.4.0 peerDependencies: react: ^16.8.4 || ^17.0.0 react-dom: ^16.8.4 || ^17.0.0 - checksum: e660575f900eedbeab6e222eb4f8ef6a7a49815c91a97839a4839737c0b3101698bf7c6e035cbafaa49010340010a9ec0d37270dc81a470b3bae42662c7a24b8 + checksum: 0be51e9a881383ed76b6e8f369ca6f7754a4f6bd59093c6d28d955b9422a25e868f24b534eb08ba84a5524ae742edd4a052813767b2ea1e8767914dceffc19b8 languageName: node linkType: hard -"@docusaurus/plugin-google-analytics@npm:2.3.1": - version: 2.3.1 - resolution: "@docusaurus/plugin-google-analytics@npm:2.3.1" +"@docusaurus/plugin-google-analytics@npm:2.4.1": + version: 2.4.1 + resolution: "@docusaurus/plugin-google-analytics@npm:2.4.1" dependencies: - "@docusaurus/core": 2.3.1 - "@docusaurus/types": 2.3.1 - "@docusaurus/utils-validation": 2.3.1 + "@docusaurus/core": 2.4.1 + "@docusaurus/types": 2.4.1 + "@docusaurus/utils-validation": 2.4.1 tslib: ^2.4.0 peerDependencies: react: ^16.8.4 || ^17.0.0 react-dom: ^16.8.4 || ^17.0.0 - checksum: 9306ae89cd5fb8ca86fd58809d7e624f988411d8908a151e9b6d9e8d0b84e08f1e3eba46024bc4321bcaeb3e9bc38e919b0bcf561adc9d40fa97c8ffeb232888 + checksum: 9e754c0bc7779867af07cd77de36f5b491671a96fba5e3517458803465c24773357eb2f1400c41c80e69524cb2c7e9e353262335051aa54192eeae9d9eb055cb languageName: node linkType: hard -"@docusaurus/plugin-google-gtag@npm:2.3.1": - version: 2.3.1 - resolution: "@docusaurus/plugin-google-gtag@npm:2.3.1" +"@docusaurus/plugin-google-gtag@npm:2.4.1": + version: 2.4.1 + resolution: "@docusaurus/plugin-google-gtag@npm:2.4.1" dependencies: - "@docusaurus/core": 2.3.1 - "@docusaurus/types": 2.3.1 - "@docusaurus/utils-validation": 2.3.1 + "@docusaurus/core": 2.4.1 + "@docusaurus/types": 2.4.1 + "@docusaurus/utils-validation": 2.4.1 tslib: ^2.4.0 peerDependencies: react: ^16.8.4 || ^17.0.0 react-dom: ^16.8.4 || ^17.0.0 - checksum: 494f0405d79aa9cb36d1ea4cf739499ad15b59fe876573ab5b304b5e84ab6ef4d428ebdc26647777b0816af452f62959b5ddb25e5bbf73c7fb3d6568258980d0 + checksum: ed529f2100599401e1c2aa772dca7c60fdb1990e44af3a9e476e1922f1370ef0dd0b5e6442f846bd942b74af63f3163ac85f1eefe1e85660b61ee60f2044c463 languageName: node linkType: hard -"@docusaurus/plugin-google-tag-manager@npm:2.3.1": - version: 2.3.1 - resolution: "@docusaurus/plugin-google-tag-manager@npm:2.3.1" +"@docusaurus/plugin-google-tag-manager@npm:2.4.1": + version: 2.4.1 + resolution: "@docusaurus/plugin-google-tag-manager@npm:2.4.1" dependencies: - "@docusaurus/core": 2.3.1 - "@docusaurus/types": 2.3.1 - "@docusaurus/utils-validation": 2.3.1 + "@docusaurus/core": 2.4.1 + "@docusaurus/types": 2.4.1 + "@docusaurus/utils-validation": 2.4.1 tslib: ^2.4.0 peerDependencies: react: ^16.8.4 || ^17.0.0 react-dom: ^16.8.4 || ^17.0.0 - checksum: d0b2ccc212652bb4f1c1bd61420e7f235325d4f2e8de0f5b25282305f54209d05da981c1253325bcae9afbc7575bd5c246d037d2be5fbda06f2842ba8335ab47 + checksum: c5c6fce9c9eeae7cbeb277b9765a67d5c4403a3e04f634aadac6d4ba9901739a547d4ea023c83a76cfece2fdb2d175851acaa69abb2f190401b612adeab5524d languageName: node linkType: hard -"@docusaurus/plugin-sitemap@npm:2.3.1": - version: 2.3.1 - resolution: "@docusaurus/plugin-sitemap@npm:2.3.1" - dependencies: - "@docusaurus/core": 2.3.1 - "@docusaurus/logger": 2.3.1 - "@docusaurus/types": 2.3.1 - "@docusaurus/utils": 2.3.1 - "@docusaurus/utils-common": 2.3.1 - "@docusaurus/utils-validation": 2.3.1 +"@docusaurus/plugin-sitemap@npm:2.4.1": + version: 2.4.1 + resolution: "@docusaurus/plugin-sitemap@npm:2.4.1" + dependencies: + "@docusaurus/core": 2.4.1 + "@docusaurus/logger": 2.4.1 + "@docusaurus/types": 2.4.1 + "@docusaurus/utils": 2.4.1 + "@docusaurus/utils-common": 2.4.1 + "@docusaurus/utils-validation": 2.4.1 fs-extra: ^10.1.0 sitemap: ^7.1.1 tslib: ^2.4.0 peerDependencies: react: ^16.8.4 || ^17.0.0 react-dom: ^16.8.4 || ^17.0.0 - checksum: 667d2abbf46efffc4d20e38fe435a19392f07726446193a017306652ee9db3d478e971eefb209e1a5c243b6b82af3de72d4b975b8e74aa93bda4711ce8c309bc + checksum: aa6728278017c047b4ed1456e349b1a267d85b4bb0c422bb63e59fc28ccb0e286dc66918f44f6ade660e550b047e2b14796c54c96fde6eb69395770cf39cf306 languageName: node linkType: hard "@docusaurus/preset-classic@npm:latest": - version: 2.3.1 - resolution: "@docusaurus/preset-classic@npm:2.3.1" - dependencies: - "@docusaurus/core": 2.3.1 - "@docusaurus/plugin-content-blog": 2.3.1 - "@docusaurus/plugin-content-docs": 2.3.1 - "@docusaurus/plugin-content-pages": 2.3.1 - "@docusaurus/plugin-debug": 2.3.1 - "@docusaurus/plugin-google-analytics": 2.3.1 - "@docusaurus/plugin-google-gtag": 2.3.1 - "@docusaurus/plugin-google-tag-manager": 2.3.1 - "@docusaurus/plugin-sitemap": 2.3.1 - "@docusaurus/theme-classic": 2.3.1 - "@docusaurus/theme-common": 2.3.1 - "@docusaurus/theme-search-algolia": 2.3.1 - "@docusaurus/types": 2.3.1 + version: 2.4.1 + resolution: "@docusaurus/preset-classic@npm:2.4.1" + dependencies: + "@docusaurus/core": 2.4.1 + "@docusaurus/plugin-content-blog": 2.4.1 + "@docusaurus/plugin-content-docs": 2.4.1 + "@docusaurus/plugin-content-pages": 2.4.1 + "@docusaurus/plugin-debug": 2.4.1 + "@docusaurus/plugin-google-analytics": 2.4.1 + "@docusaurus/plugin-google-gtag": 2.4.1 + "@docusaurus/plugin-google-tag-manager": 2.4.1 + "@docusaurus/plugin-sitemap": 2.4.1 + "@docusaurus/theme-classic": 2.4.1 + "@docusaurus/theme-common": 2.4.1 + "@docusaurus/theme-search-algolia": 2.4.1 + "@docusaurus/types": 2.4.1 peerDependencies: react: ^16.8.4 || ^17.0.0 react-dom: ^16.8.4 || ^17.0.0 - checksum: e4128a1bcb64d5ced04a281476ec1ae2d5523040d41ed57a8f744fb83659a2a2be902e94989de69ab1e6d693ec26c60d1ef6b2fe3ec96d5af6c9b3ef58f5b0cd + checksum: bad7f237ac03a9bc6206cb7a5d077d85d5a6316d34ff089c487ce92b8f6103ef22b04f35d637bdc31dddabd97d5babb1817852b9b97db7c33f3d4c7f33cb149d languageName: node linkType: hard @@ -2023,26 +2023,26 @@ __metadata: languageName: node linkType: hard -"@docusaurus/theme-classic@npm:2.3.1": - version: 2.3.1 - resolution: "@docusaurus/theme-classic@npm:2.3.1" - dependencies: - "@docusaurus/core": 2.3.1 - "@docusaurus/mdx-loader": 2.3.1 - "@docusaurus/module-type-aliases": 2.3.1 - "@docusaurus/plugin-content-blog": 2.3.1 - "@docusaurus/plugin-content-docs": 2.3.1 - "@docusaurus/plugin-content-pages": 2.3.1 - "@docusaurus/theme-common": 2.3.1 - "@docusaurus/theme-translations": 2.3.1 - "@docusaurus/types": 2.3.1 - "@docusaurus/utils": 2.3.1 - "@docusaurus/utils-common": 2.3.1 - "@docusaurus/utils-validation": 2.3.1 +"@docusaurus/theme-classic@npm:2.4.1": + version: 2.4.1 + resolution: "@docusaurus/theme-classic@npm:2.4.1" + dependencies: + "@docusaurus/core": 2.4.1 + "@docusaurus/mdx-loader": 2.4.1 + "@docusaurus/module-type-aliases": 2.4.1 + "@docusaurus/plugin-content-blog": 2.4.1 + "@docusaurus/plugin-content-docs": 2.4.1 + "@docusaurus/plugin-content-pages": 2.4.1 + "@docusaurus/theme-common": 2.4.1 + "@docusaurus/theme-translations": 2.4.1 + "@docusaurus/types": 2.4.1 + "@docusaurus/utils": 2.4.1 + "@docusaurus/utils-common": 2.4.1 + "@docusaurus/utils-validation": 2.4.1 "@mdx-js/react": ^1.6.22 clsx: ^1.2.1 copy-text-to-clipboard: ^3.0.1 - infima: 0.2.0-alpha.42 + infima: 0.2.0-alpha.43 lodash: ^4.17.21 nprogress: ^0.2.0 postcss: ^8.4.14 @@ -2055,20 +2055,21 @@ __metadata: peerDependencies: react: ^16.8.4 || ^17.0.0 react-dom: ^16.8.4 || ^17.0.0 - checksum: 273812924fc29b2316aff554ae0302509ebeaca5aa829b58253e74d22a66e69444f1c324a2d5e8e170e6c6f27dd0d6927e6c6a22a7e0c14567ff777d04a5b0c1 + checksum: 058875d4c60f77f86b5d679b1ef99ed06101411f003d2d65fa4fe5ae6fbe5e5e6a291616268a18a29fdd84f0853cc4219a2c1801663b75f27c664b3ace7d009e languageName: node linkType: hard -"@docusaurus/theme-common@npm:2.3.1": - version: 2.3.1 - resolution: "@docusaurus/theme-common@npm:2.3.1" - dependencies: - "@docusaurus/mdx-loader": 2.3.1 - "@docusaurus/module-type-aliases": 2.3.1 - "@docusaurus/plugin-content-blog": 2.3.1 - "@docusaurus/plugin-content-docs": 2.3.1 - "@docusaurus/plugin-content-pages": 2.3.1 - "@docusaurus/utils": 2.3.1 +"@docusaurus/theme-common@npm:2.4.1": + version: 2.4.1 + resolution: "@docusaurus/theme-common@npm:2.4.1" + dependencies: + "@docusaurus/mdx-loader": 2.4.1 + "@docusaurus/module-type-aliases": 2.4.1 + "@docusaurus/plugin-content-blog": 2.4.1 + "@docusaurus/plugin-content-docs": 2.4.1 + "@docusaurus/plugin-content-pages": 2.4.1 + "@docusaurus/utils": 2.4.1 + "@docusaurus/utils-common": 2.4.1 "@types/history": ^4.7.11 "@types/react": "*" "@types/react-router-config": "*" @@ -2081,22 +2082,22 @@ __metadata: peerDependencies: react: ^16.8.4 || ^17.0.0 react-dom: ^16.8.4 || ^17.0.0 - checksum: 6b902e9e782721c3c49bcdee5d969ea1c1138ebcb03891e34f827b16f2c06f43a86d95f240a60ed084539e9b16435312a41be7bff4e724f4fb209998dd4d3a59 + checksum: 206db83caab59eadc5b8e5394d46b64ec8695bd20d4a3defe111c28094faf6de92481c3bb4e54c159a519bc782759031b121e17d7e0175d873a843f36630c539 languageName: node linkType: hard -"@docusaurus/theme-search-algolia@npm:2.3.1": - version: 2.3.1 - resolution: "@docusaurus/theme-search-algolia@npm:2.3.1" +"@docusaurus/theme-search-algolia@npm:2.4.1": + version: 2.4.1 + resolution: "@docusaurus/theme-search-algolia@npm:2.4.1" dependencies: "@docsearch/react": ^3.1.1 - "@docusaurus/core": 2.3.1 - "@docusaurus/logger": 2.3.1 - "@docusaurus/plugin-content-docs": 2.3.1 - "@docusaurus/theme-common": 2.3.1 - "@docusaurus/theme-translations": 2.3.1 - "@docusaurus/utils": 2.3.1 - "@docusaurus/utils-validation": 2.3.1 + "@docusaurus/core": 2.4.1 + "@docusaurus/logger": 2.4.1 + "@docusaurus/plugin-content-docs": 2.4.1 + "@docusaurus/theme-common": 2.4.1 + "@docusaurus/theme-translations": 2.4.1 + "@docusaurus/utils": 2.4.1 + "@docusaurus/utils-validation": 2.4.1 algoliasearch: ^4.13.1 algoliasearch-helper: ^3.10.0 clsx: ^1.2.1 @@ -2108,23 +2109,23 @@ __metadata: peerDependencies: react: ^16.8.4 || ^17.0.0 react-dom: ^16.8.4 || ^17.0.0 - checksum: 965303068e43b11f58d20b95bb6dfc01d5e575c2070d2730b94303bd2a1d33794075cae43bfe472f08061bd8770f14c8eb54932274e6b39f954ab34e7cfc5689 + checksum: 00016804462e3ca961de96f477c397bf68bbfa7c641cfb95e76492ec00f2e0f8f5b19623cd6ad0fda31ad08aa29fa1a74185d9bd34f61437e7f36f711064f3ba languageName: node linkType: hard -"@docusaurus/theme-translations@npm:2.3.1": - version: 2.3.1 - resolution: "@docusaurus/theme-translations@npm:2.3.1" +"@docusaurus/theme-translations@npm:2.4.1": + version: 2.4.1 + resolution: "@docusaurus/theme-translations@npm:2.4.1" dependencies: fs-extra: ^10.1.0 tslib: ^2.4.0 - checksum: dd3796be63c4c946af789c3da18ed2704a2fa90d8e752ba2b780a124dc13369ba590218afad0ac4ea2342f7331ccb9eb1be086226c950b8384978d94a15c57ad + checksum: cf21cd01db6426ccc29360fe9caca39e61ee5efde3796539e8292e212c25727227970f935050f294f0ab475f201720e32a1d09a7e40f2b08f56f69282f660da8 languageName: node linkType: hard -"@docusaurus/types@npm:2.3.1": - version: 2.3.1 - resolution: "@docusaurus/types@npm:2.3.1" +"@docusaurus/types@npm:2.4.1": + version: 2.4.1 + resolution: "@docusaurus/types@npm:2.4.1" dependencies: "@types/history": ^4.7.11 "@types/react": "*" @@ -2137,13 +2138,13 @@ __metadata: peerDependencies: react: ^16.8.4 || ^17.0.0 react-dom: ^16.8.4 || ^17.0.0 - checksum: 91e52f37b97964112aa0d50ee4a6f534d7da941443af5ddc96418817c6ce532a98c73e67045ac703b582c7ed703ebb360205eec30da7f738c0105f2b3ae1a246 + checksum: d44e91c9153802a5c63a0bd91e56654f901df837ac7b380dff8f165991728e88d29efa7c64c6522d0afdf8ec845613aff5867badb717d29b691392712f655936 languageName: node linkType: hard -"@docusaurus/utils-common@npm:2.3.1": - version: 2.3.1 - resolution: "@docusaurus/utils-common@npm:2.3.1" +"@docusaurus/utils-common@npm:2.4.1": + version: 2.4.1 + resolution: "@docusaurus/utils-common@npm:2.4.1" dependencies: tslib: ^2.4.0 peerDependencies: @@ -2151,28 +2152,28 @@ __metadata: peerDependenciesMeta: "@docusaurus/types": optional: true - checksum: 405dc5b8aba9a97b2670ba8ff3911bbdaed274edc15214ab482a7159a07ad1c9b3198835a7bee42de4e0320d42bd402ed89ae6896744a364d64d89d9f78bcfb0 + checksum: 475f05b94a879d9078564dbb081d91a953356f54d0a4384a8711281a62851b58d99df9b45664a30e40ac37733772cedd53a253d34a07fbd5b36bcce46ab200c8 languageName: node linkType: hard -"@docusaurus/utils-validation@npm:2.3.1": - version: 2.3.1 - resolution: "@docusaurus/utils-validation@npm:2.3.1" +"@docusaurus/utils-validation@npm:2.4.1": + version: 2.4.1 + resolution: "@docusaurus/utils-validation@npm:2.4.1" dependencies: - "@docusaurus/logger": 2.3.1 - "@docusaurus/utils": 2.3.1 + "@docusaurus/logger": 2.4.1 + "@docusaurus/utils": 2.4.1 joi: ^17.6.0 js-yaml: ^4.1.0 tslib: ^2.4.0 - checksum: 1e5529d1d0c4fcd9006adf2e5b545458a7dba3877563fb444dcec472f27a3d8492d4c6fb5dd1071bb6e668a13a845d74b8f6c4b6387babfa0e467a9b8b237fda + checksum: 44dc482770ea3932e68e58c0bb9503e1b3c73ce28565c438b0d68a7427ee91760ca84a5e150dfc4e04497e072fe4394050dd2af4c4ff43a227b1464e89d705a0 languageName: node linkType: hard -"@docusaurus/utils@npm:2.3.1": - version: 2.3.1 - resolution: "@docusaurus/utils@npm:2.3.1" +"@docusaurus/utils@npm:2.4.1": + version: 2.4.1 + resolution: "@docusaurus/utils@npm:2.4.1" dependencies: - "@docusaurus/logger": 2.3.1 + "@docusaurus/logger": 2.4.1 "@svgr/webpack": ^6.2.1 escape-string-regexp: ^4.0.0 file-loader: ^6.2.0 @@ -2193,7 +2194,7 @@ __metadata: peerDependenciesMeta: "@docusaurus/types": optional: true - checksum: e8bce9bbd98bf63664fcd7c0a5f8dec30dad31ed19e18d724b43189b04ecdc1174537e1d987293575ec18d421236fb92d3d39d28477e921507260a39c3f6d6d0 + checksum: 4c7e49cabe6650b027c0f698344532fb81c8aaf912f17a287fad986e008ce7c6d34d372b44974488ce160ae43ef6aad4ac7b2790bc3fe2ab05ef5fa7b9506ce9 languageName: node linkType: hard @@ -6394,10 +6395,10 @@ __metadata: languageName: node linkType: hard -"infima@npm:0.2.0-alpha.42": - version: 0.2.0-alpha.42 - resolution: "infima@npm:0.2.0-alpha.42" - checksum: 7206f36639c00a08daab811fedc748068951497efb5ec948cba846fb23856443668015f6bd65ddebe857cc2235f6ca98429f7018c73dcac47b0361ef4721bb8f +"infima@npm:0.2.0-alpha.43": + version: 0.2.0-alpha.43 + resolution: "infima@npm:0.2.0-alpha.43" + checksum: fc5f79240e940eddd750439511767092ccb4051e5e91d253ec7630a9e7ce691812da3aa0f05e46b4c0a95dbfadeae5714fd0073f8d2df12e5aaff0697a1d6aa2 languageName: node linkType: hard From a5f2618c127dd87f0cb8d87eb4ddf9d71cb48a76 Mon Sep 17 00:00:00 2001 From: Arik Smith Date: Thu, 24 Aug 2023 18:21:33 -0400 Subject: [PATCH 222/251] enables Ali Cloud estimates within app --- packages/ali/src/application/index.ts | 2 +- packages/app/src/App.ts | 21 ++++- packages/app/src/__tests__/App.test.ts | 122 +++++++++++++++++++++++-- 3 files changed, 132 insertions(+), 13 deletions(-) diff --git a/packages/ali/src/application/index.ts b/packages/ali/src/application/index.ts index 9a24de737..5ec9af71e 100644 --- a/packages/ali/src/application/index.ts +++ b/packages/ali/src/application/index.ts @@ -1,4 +1,4 @@ /* * © 2023 Thoughtworks, Inc. */ -export { default as ALIAccount } from './AliAccount' +export { default as AliAccount } from './AliAccount' diff --git a/packages/app/src/App.ts b/packages/app/src/App.ts index ac45dfe02..abde62c7b 100644 --- a/packages/app/src/App.ts +++ b/packages/app/src/App.ts @@ -25,11 +25,13 @@ import { AWSAccount, } from '@cloud-carbon-footprint/aws' import { GCPAccount, getGCPEmissionsFactors } from '@cloud-carbon-footprint/gcp' +import { AliAccount } from '@cloud-carbon-footprint/ali' import { OnPremise } from '@cloud-carbon-footprint/on-premise' import cache from './Cache' import { EstimationRequest, RecommendationRequest } from './CreateValidRequest' import { includeCloudProviders } from './common/helpers' +import { ALI_EMISSIONS_FACTORS_METRIC_TON_PER_KWH } from '@cloud-carbon-footprint/ali' export const recommendationsMockPath = 'recommendations.mock.json' @@ -43,7 +45,7 @@ export default class App { const grouping = request.groupBy as GroupBy const config = configLoader() includeCloudProviders(cloudProviderToSeed, config) - const { AWS, GCP, AZURE } = config + const { AWS, GCP, AZURE, ALI } = config if (process.env.TEST_MODE) { return [] } @@ -114,11 +116,25 @@ export default class App { appLogger.info('Finished Azure Estimations') } + const AliEstimates: EstimationResult[][] = [] + if (ALI.INCLUDE_ESTIMATES && ALI.authentication?.accessKeyId) { + appLogger.info('Starting Ali Cloud Estimations') + const aliAccount = new AliAccount() + const estimates = await aliAccount.getDataFromCostAndUsageReports( + startDate, + endDate, + grouping, + ) + AliEstimates.push(estimates) + appLogger.info('Finished Ali Cloud Estimations') + } + return reduceByTimestamp( AWSEstimatesByRegion.flat() .flat() .concat(GCPEstimatesByRegion.flat()) - .concat(AzureEstimatesByRegion.flat()), + .concat(AzureEstimatesByRegion.flat()) + .concat(AliEstimates.flat()), ) } @@ -127,6 +143,7 @@ export default class App { AWS: AWS_EMISSIONS_FACTORS_METRIC_TON_PER_KWH, GCP: getGCPEmissionsFactors(), AZURE: AZURE_EMISSIONS_FACTORS_METRIC_TON_PER_KWH, + ALI: ALI_EMISSIONS_FACTORS_METRIC_TON_PER_KWH, } return Object.entries( diff --git a/packages/app/src/__tests__/App.test.ts b/packages/app/src/__tests__/App.test.ts index e1aa3915b..b2ba516fa 100644 --- a/packages/app/src/__tests__/App.test.ts +++ b/packages/app/src/__tests__/App.test.ts @@ -22,6 +22,7 @@ import { import { AWSAccount } from '@cloud-carbon-footprint/aws' import { GCPAccount } from '@cloud-carbon-footprint/gcp' import { AzureAccount } from '@cloud-carbon-footprint/azure' +import { AliAccount } from '@cloud-carbon-footprint/ali' import App from '../App' import { EstimationRequest, RecommendationRequest } from '../CreateValidRequest' import cache from '../Cache' @@ -43,9 +44,9 @@ const initializeAzureAccount = jest.spyOn( AzureAccount.prototype, 'initializeAccount', ) -const getServices = jest.spyOn(AWSAccount.prototype, 'getServices') -const getGCPServices = jest.spyOn(GCPAccount.prototype, 'getServices') +const getAWSServices = jest.spyOn(AWSAccount.prototype, 'getServices') +const getGCPServices = jest.spyOn(GCPAccount.prototype, 'getServices') const defaultAWSConfigLoader = { INCLUDE_ESTIMATES: true, accounts: [{ id: '12345678', name: 'test AWS account' }], @@ -74,6 +75,14 @@ const defaultGCPConfigLoader = { CACHE_BUCKET_NAME: 'test-bucket-name', } +const defaultAliConfigLoader = { + NAME: 'AliCloud', + authentication: { + accessKeyId: 'test-access-key-id', + accessKeySecret: 'test-access-key-secret', + }, +} + jest.mock('../Cache') jest.mock('@cloud-carbon-footprint/common', () => ({ ...(jest.requireActual('@cloud-carbon-footprint/common') as Record< @@ -90,6 +99,7 @@ jest.mock('@cloud-carbon-footprint/common', () => ({ return { AWS: defaultAWSConfigLoader, GCP: defaultGCPConfigLoader, + ALI: defaultAliConfigLoader, } }), })) @@ -127,6 +137,17 @@ jest.mock('@cloud-carbon-footprint/azure', () => ({ }, })) +jest.mock('@cloud-carbon-footprint/ali', () => ({ + ...(jest.requireActual('@cloud-carbon-footprint/ali') as Record< + string, + unknown + >), + ALI_EMISSIONS_FACTORS_METRIC_TON_PER_KWH: { + aliRegion1: 4, + aliRegion2: 2, + }, +})) + const testRegions = ['us-east-1', 'us-east-2'] describe('App', () => { @@ -147,6 +168,8 @@ describe('App', () => { const testGcpAccountIdTwo = '11223344' const testGcpAccountNameOne = 'test GCP account' const testGcpAccountNameTwo = 'test GCP account 2' + const testAliAccountId = 'test-account-id' + const testAliAccountName = 'test-account-name' beforeEach(() => { app = new App() @@ -157,6 +180,7 @@ describe('App', () => { ...configLoader(), AWS: defaultAWSConfigLoader, GCP: defaultGCPConfigLoader, + ALI: defaultAliConfigLoader, }) }) @@ -193,6 +217,16 @@ describe('App', () => { region: 'azureRegion2', mtPerKwHour: 6, }, + { + cloudProvider: 'ALI', + region: 'aliRegion1', + mtPerKwHour: 4, + }, + { + cloudProvider: 'ALI', + region: 'aliRegion2', + mtPerKwHour: 2, + }, ] // when const response = app.getEmissionsFactors() @@ -206,7 +240,7 @@ describe('App', () => { const mockGetEstimates: jest.Mock> = jest.fn() setUpServices( - getServices as jest.Mock, + getAWSServices as jest.Mock, [mockGetEstimates], ['serviceOne'], [jest.fn().mockResolvedValue([])], @@ -300,7 +334,7 @@ describe('App', () => { const mockGetCostPerService2: jest.Mock> = jest.fn() setUpServices( - getServices as jest.Mock, + getAWSServices as jest.Mock, [mockGetEstimates, mockGetEstimates2], ['serviceOne', 'serviceTwo'], [mockGetCostPerService1, mockGetCostPerService2], @@ -430,7 +464,7 @@ describe('App', () => { const mockGetAWSEstimates: jest.Mock> = jest.fn() setUpServices( - getServices as jest.Mock, + getAWSServices as jest.Mock, [mockGetAWSEstimates], ['serviceOne'], [jest.fn().mockResolvedValue([])], @@ -658,6 +692,10 @@ describe('App', () => { INCLUDE_ESTIMATES: false, CURRENT_REGIONS: ['us-east1'], }, + ALI: { + ...defaultAliConfigLoader, + INCLUDE_ESTIMATES: false, + }, }) }) @@ -667,7 +705,7 @@ describe('App', () => { > = jest.fn() const mockGetCostPerService: jest.Mock> = jest.fn() setUpServices( - getServices as jest.Mock, + getAWSServices as jest.Mock, [mockGetCostAndEstimatesPerService], ['ebs'], [mockGetCostPerService], @@ -734,7 +772,7 @@ describe('App', () => { const mockGetCostPerService1: jest.Mock> = jest.fn() const mockGetCostPerService2: jest.Mock> = jest.fn() setUpServices( - getServices as jest.Mock, + getAWSServices as jest.Mock, [mockGetEstimates1, mockGetEstimates2], ['serviceOne', 'serviceTwo'], [mockGetCostPerService1, mockGetCostPerService2], @@ -819,7 +857,7 @@ describe('App', () => { const mockGetEstimates: jest.Mock> = jest.fn() setUpServices( - getServices as jest.Mock, + getAWSServices as jest.Mock, [mockGetEstimates], ['serviceOne'], [jest.fn().mockResolvedValue([])], @@ -875,7 +913,7 @@ describe('App', () => { Promise > = jest.fn() setUpServices( - getServices as jest.Mock, + getAWSServices as jest.Mock, [mockGetCostAndEstimatesPerService], ['ebs'], [jest.fn().mockResolvedValue([])], @@ -891,7 +929,7 @@ describe('App', () => { Promise > = jest.fn() setUpServices( - getServices as jest.Mock, + getAWSServices as jest.Mock, [mockGetCostAndEstimatesPerService], ['ebs'], [jest.fn().mockResolvedValue([])], @@ -943,6 +981,70 @@ describe('App', () => { expect(estimationResult).toEqual(expectedEstimationResults) }) + + // TODO: Refactor tests to be separated by cloud providers + usage approaches + describe('AliCloud', () => { + it('gets cost and estimates using billing data', async () => { + ;(configLoader as jest.Mock).mockReturnValue({ + ...configLoader(), + AWS: { + INCLUDE_ESTIMATES: false, + }, + GCP: { + INCLUDE_ESTIMATES: false, + }, + ALI: { + ...defaultAliConfigLoader, + INCLUDE_ESTIMATES: true, + }, + }) + + const mockEstimationResults: EstimationResult[] = [ + { + timestamp: new Date(startDate), + serviceEstimates: [ + { + accountId: testAliAccountId, + accountName: testAliAccountName, + cloudProvider: 'AliCloud', + co2e: 0, + cost: 0, + region: 'CN_HANGZHOU', + serviceName: 'ECS', + usesAverageCPUConstant: false, + kilowattHours: 0, + }, + ], + groupBy: grouping, + periodEndDate: new Date('2020-08-07T23:59:59.000Z'), + periodStartDate: new Date('2020-08-07T00:00:00.000Z'), + }, + ] + + const mockGetDataFromCostAndUsageReports = jest + .spyOn(AliAccount.prototype, 'getDataFromCostAndUsageReports') + .mockResolvedValue(mockEstimationResults) + + const start = moment(startDate).toDate() + const end = moment(startDate).add(1, 'day').toDate() + const request: EstimationRequest = { + startDate: start, + endDate: end, + ignoreCache: false, + groupBy: grouping, + } + + const result = await app.getCostAndEstimates(request) + + expect(mockGetDataFromCostAndUsageReports).toHaveBeenCalledWith( + request.startDate, + request.endDate, + request.groupBy, + ) + + expect(result).toEqual(mockEstimationResults) + }) + }) }) describe('recommendations', () => { From 3b71a0d4a2b5c8eac91298905fc05cdd6a068ccd Mon Sep 17 00:00:00 2001 From: Arik Smith Date: Thu, 24 Aug 2023 18:59:18 -0400 Subject: [PATCH 223/251] removes double brackets from shell script conditionals for better environment compatibility --- .husky/pre-commit | 4 ++-- scripts/branch_warning.sh | 4 ++-- scripts/copyright_check.sh | 10 +++++----- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.husky/pre-commit b/.husky/pre-commit index 383189262..8d91be5d6 100755 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -4,13 +4,13 @@ branch="$(git rev-parse --abbrev-ref HEAD)" root=$( git diff --pretty="" --name-only --staged -- packages/ | wc -l ) -if [[ "$branch" != "changeset-release/trunk" ]]; then +if [ "$branch" != "changeset-release/trunk" ]; then if [ ! -t 1 ]; then export TALISMAN_INTERACTIVE="false" fi ./scripts/copyright_check.sh && ./scripts/branch_warning.sh && $TALISMAN_HOME/talisman_hook_script pre-commit || exit 1 - if [[ "$root" -gt "0" ]]; then + if [ "$root" -gt "0" ]; then echo 'Detecting modified files in packages directory...' yarn lerna run precommit --stream fi diff --git a/scripts/branch_warning.sh b/scripts/branch_warning.sh index 67297b473..1ff9429f6 100755 --- a/scripts/branch_warning.sh +++ b/scripts/branch_warning.sh @@ -6,7 +6,7 @@ branch="$(git rev-parse --abbrev-ref HEAD)" -if [[ "$branch" != "trunk" ]]; then +if [ "$branch" != "trunk" ]; then if [ -t 1 ]; then # We're running interactive read -p "Are you sure you want to use a branch that isn't trunk? (y/n): " -n 1 -r < /dev/tty @@ -16,7 +16,7 @@ if [[ "$branch" != "trunk" ]]; then REPLY="Y" fi echo - if [[ $REPLY =~ ^[Yy]$ ]]; then + if [ $REPLY =~ ^[Yy]$ ]; then echo "Okie dokie. Be Careful out there :)" exit 0 fi diff --git a/scripts/copyright_check.sh b/scripts/copyright_check.sh index d4b150f29..4a608624b 100755 --- a/scripts/copyright_check.sh +++ b/scripts/copyright_check.sh @@ -7,15 +7,15 @@ copyright="© 202[1-3] Thoughtworks, Inc." errorCode=0 domain=`git config user.email` -if [[ "$domain" != *"thoughtworks.com"* ]]; then echo "Thank you for contributing!"; +if [ "$domain" != *"thoughtworks.com"* ]; then echo "Thank you for contributing!"; else git diff --cached --name-status | while read x file; do if [ "$x" = 'D' ]; then continue; fi if [ ${x::1} = 'R' ]; then continue; fi - if [[ $file = *"CHANGELOG"* ]]; then continue; fi - if [[ $file = *".changeset"* ]]; then continue; fi - if [[ $file = *"microsite/"* ]]; then continue; fi - if [[ $file = *".adr/"* ]]; then continue; fi + if [ $file = *"CHANGELOG"* ]; then continue; fi + if [ $file = *".changeset"* ]; then continue; fi + if [ $file = *"microsite/"* ]; then continue; fi + if [ $file = *".adr/"* ]; then continue; fi holder=$(echo $file | grep -E "^.*\.(ts|tsx|js|jsx|md|sh)$" | wc -l) if [ $holder -eq 0 ]; then continue; fi if grep -E "$copyright" $file; then continue; fi From dd98c8cbfe46d892ff23f5ac5c8a680230dd97b8 Mon Sep 17 00:00:00 2001 From: Arik Smith Date: Thu, 24 Aug 2023 19:17:55 -0400 Subject: [PATCH 224/251] [changeset] Enables Ali Cloud ECS estimates in the CCF Application --- .changeset/kind-windows-mate.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changeset/kind-windows-mate.md diff --git a/.changeset/kind-windows-mate.md b/.changeset/kind-windows-mate.md new file mode 100644 index 000000000..6062f2767 --- /dev/null +++ b/.changeset/kind-windows-mate.md @@ -0,0 +1,6 @@ +--- +'@cloud-carbon-footprint/app': minor +'@cloud-carbon-footprint/ali': patch +--- + +Enables Ali Cloud ECS estimates in the CCF Application From 73d3342fe3ec7a106fb7177a93fc66f1e572b494 Mon Sep 17 00:00:00 2001 From: Arik Smith Date: Mon, 28 Aug 2023 12:26:09 -0400 Subject: [PATCH 225/251] bump testcafe and fix type issue causing tests to hang --- packages/ali/src/lib/AliCalculateRow.ts | 2 +- packages/integration-tests/package.json | 2 +- yarn.lock | 112 +++++++++++------------- 3 files changed, 52 insertions(+), 64 deletions(-) diff --git a/packages/ali/src/lib/AliCalculateRow.ts b/packages/ali/src/lib/AliCalculateRow.ts index b771ff29f..e8e8f7359 100644 --- a/packages/ali/src/lib/AliCalculateRow.ts +++ b/packages/ali/src/lib/AliCalculateRow.ts @@ -132,7 +132,7 @@ export default class AliCalculateRow extends BillingDataRow { } private getUsage(servicePeriod: string, servicePeriodUnit: string) { - const servicePeriodUnitRatios = { + const servicePeriodUnitRatios: { [key: string]: number } = { 秒: 1 / 3600, 分: 1 / 60, 时: 1, diff --git a/packages/integration-tests/package.json b/packages/integration-tests/package.json index 71789ca37..4c9f3a5b4 100644 --- a/packages/integration-tests/package.json +++ b/packages/integration-tests/package.json @@ -24,7 +24,7 @@ "eslint-plugin-testcafe": "^0.2.1", "lint-staged": "^12.1.7", "prettier": "^2.5.1", - "testcafe": "^3.1.0", + "testcafe": "^3.2.0", "wait-on": "^6.0.0" }, "lint-staged": { diff --git a/yarn.lock b/yarn.lock index ed5328c45..4083e4765 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12,6 +12,13 @@ __metadata: languageName: node linkType: hard +"@adobe/css-tools@npm:^4.3.0-rc.1": + version: 4.3.1 + resolution: "@adobe/css-tools@npm:4.3.1" + checksum: ad43456379ff391132aff687ece190cb23ea69395e23c9b96690eeabe2468da89a4aaf266e4f8b6eaab53db3d1064107ce0f63c3a974e864f4a04affc768da3f + languageName: node + linkType: hard + "@alicloud/bssopenapi20171214@npm:^1.0.14": version: 1.0.14 resolution: "@alicloud/bssopenapi20171214@npm:1.0.14" @@ -2781,7 +2788,7 @@ __metadata: eslint-plugin-testcafe: ^0.2.1 lint-staged: ^12.1.7 prettier: ^2.5.1 - testcafe: ^3.1.0 + testcafe: ^3.2.0 wait-on: ^6.0.0 languageName: unknown linkType: soft @@ -3003,12 +3010,18 @@ __metadata: languageName: node linkType: hard -"@devexpress/error-stack-parser@npm:^2.0.6": - version: 2.0.6 - resolution: "@devexpress/error-stack-parser@npm:2.0.6" +"@devexpress/callsite-record@npm:^4.1.6": + version: 4.1.7 + resolution: "@devexpress/callsite-record@npm:4.1.7" dependencies: - stackframe: ^1.1.1 - checksum: c1e51823aacc1eaa5a4a8bbab3cf9bc510cc4d35763f52eae61b46e8ed651aae2da88e6629bb9f1797560ec2cd4016f5343df7e771f879722fad095ed377ccbe + "@types/lodash": ^4.14.72 + callsite: ^1.0.0 + chalk: ^2.4.0 + error-stack-parser: ^2.1.4 + highlight-es: ^1.0.0 + lodash: 4.6.1 || ^4.16.1 + pinkie-promise: ^2.0.0 + checksum: 91c7b0f0786891852aa2cc8b38433a57a1c33a594fd2b43b0deead2ca090d70decf1de8f4ce9ee5923a839982240bacbc48792673f454752ade305272404f008 languageName: node linkType: hard @@ -5958,15 +5971,6 @@ __metadata: languageName: node linkType: hard -"@types/error-stack-parser@npm:^2.0.0": - version: 2.0.0 - resolution: "@types/error-stack-parser@npm:2.0.0" - dependencies: - error-stack-parser: "*" - checksum: 108188a619437b481dd67225bd6d420dd3bd6b38ff96c96ce0ce542497cbc7cab68fe773c2ecaaa282bb007f3c978b659104a0c97482a3488d3dd58bb1ca0331 - languageName: node - linkType: hard - "@types/eslint-scope@npm:^3.7.3": version: 3.7.4 resolution: "@types/eslint-scope@npm:3.7.4" @@ -8555,22 +8559,6 @@ __metadata: languageName: node linkType: hard -"callsite-record@npm:^4.0.0": - version: 4.1.4 - resolution: "callsite-record@npm:4.1.4" - dependencies: - "@devexpress/error-stack-parser": ^2.0.6 - "@types/error-stack-parser": ^2.0.0 - "@types/lodash": ^4.14.72 - callsite: ^1.0.0 - chalk: ^2.4.0 - highlight-es: ^1.0.0 - lodash: 4.6.1 || ^4.16.1 - pinkie-promise: ^2.0.0 - checksum: f38b3cec28485d6fabfb08a2af6d9b9a085f3a59a6ed4b39532a7c8786abb447952f910d5a2eeac8346e8ea12e085c80959417fcaf9e244570261370f42923a6 - languageName: node - linkType: hard - "callsite@npm:^1.0.0": version: 1.0.0 resolution: "callsite@npm:1.0.0" @@ -10973,6 +10961,13 @@ __metadata: languageName: node linkType: hard +"entities@npm:^4.4.0": + version: 4.5.0 + resolution: "entities@npm:4.5.0" + checksum: 853f8ebd5b425d350bffa97dd6958143179a5938352ccae092c62d1267c4e392a039be1bae7d51b6e4ffad25f51f9617531fedf5237f15df302ccfb452cbf2d7 + languageName: node + linkType: hard + "entities@npm:~2.1.0": version: 2.1.0 resolution: "entities@npm:2.1.0" @@ -11022,7 +11017,7 @@ __metadata: languageName: node linkType: hard -"error-stack-parser@npm:*, error-stack-parser@npm:^2.0.6": +"error-stack-parser@npm:^2.0.6, error-stack-parser@npm:^2.1.4": version: 2.1.4 resolution: "error-stack-parser@npm:2.1.4" dependencies: @@ -11031,15 +11026,6 @@ __metadata: languageName: node linkType: hard -"error-stack-parser@npm:^1.3.6": - version: 1.3.6 - resolution: "error-stack-parser@npm:1.3.6" - dependencies: - stackframe: ^0.3.1 - checksum: 0bfbb5e234a8b23091c34f796697f4e7b4f805ca90bdc8b57d144f79051b834c8590917f5167a8f49c0f90206c8fb986bf5b28fe3ea8efa201f09caf3f0a7af2 - languageName: node - linkType: hard - "errorhandler@npm:^1.5.1": version: 1.5.1 resolution: "errorhandler@npm:1.5.1" @@ -17963,6 +17949,15 @@ __metadata: languageName: node linkType: hard +"parse5@npm:^7.1.2": + version: 7.1.2 + resolution: "parse5@npm:7.1.2" + dependencies: + entities: ^4.4.0 + checksum: 59465dd05eb4c5ec87b76173d1c596e152a10e290b7abcda1aecf0f33be49646ea74840c69af975d7887543ea45564801736356c568d6b5e71792fd0f4055713 + languageName: node + linkType: hard + "parseurl@npm:~1.3.2, parseurl@npm:~1.3.3": version: 1.3.3 resolution: "parseurl@npm:1.3.3" @@ -21663,14 +21658,7 @@ __metadata: languageName: node linkType: hard -"stackframe@npm:^0.3.1": - version: 0.3.1 - resolution: "stackframe@npm:0.3.1" - checksum: 510123ec2d9a02dba8153091f0b0c760141ae4ec4be9512e74b83333e39a762380f13d9afcb469fd9ce978bc07fb5181d6f28970a18ae4070bf21d1e4caa4698 - languageName: node - linkType: hard - -"stackframe@npm:^1.1.1, stackframe@npm:^1.3.4": +"stackframe@npm:^1.3.4": version: 1.3.4 resolution: "stackframe@npm:1.3.4" checksum: bae1596873595c4610993fa84f86a3387d67586401c1816ea048c0196800c0646c4d2da98c2ee80557fd9eff05877efe33b91ba6cd052658ed96ddc85d19067d @@ -22518,15 +22506,15 @@ __metadata: languageName: node linkType: hard -"testcafe-hammerhead@npm:31.4.11": - version: 31.4.11 - resolution: "testcafe-hammerhead@npm:31.4.11" +"testcafe-hammerhead@npm:31.4.15": + version: 31.4.15 + resolution: "testcafe-hammerhead@npm:31.4.15" dependencies: + "@adobe/css-tools": ^4.3.0-rc.1 "@electron/asar": ^3.2.3 acorn-hammerhead: 0.6.2 bowser: 1.6.0 crypto-md5: ^1.0.0 - css: 2.2.3 debug: 4.3.1 esotope-hammerhead: 0.6.4 http-cache-semantics: ^4.1.0 @@ -22540,14 +22528,14 @@ __metadata: mustache: ^2.1.1 nanoid: ^3.1.12 os-family: ^1.0.0 - parse5: 2.2.3 + parse5: ^7.1.2 pinkie: 2.0.4 read-file-relative: ^1.2.0 semver: 7.5.3 tough-cookie: 4.1.3 tunnel-agent: 0.6.0 ws: ^7.4.6 - checksum: b1085d24ba09fbf412b11d8b4d6113b84d0270bf9d0817a881cde83d91bf41709002914757c88c874f11664de78f0dcf083bed3042f299312e25ce1bbc0fc6ec + checksum: 182470a3c709c32747996008e2fa0857a6658f3a59ec6018bd10172fd6c9d442d0300fe3019156b6bc57f605e75f567ee5b690fbde4a513245697c09826c21a0 languageName: node linkType: hard @@ -22654,9 +22642,9 @@ __metadata: languageName: node linkType: hard -"testcafe@npm:^3.1.0": - version: 3.1.0 - resolution: "testcafe@npm:3.1.0" +"testcafe@npm:^3.2.0": + version: 3.2.0 + resolution: "testcafe@npm:3.2.0" dependencies: "@babel/core": ^7.12.1 "@babel/plugin-proposal-async-generator-functions": ^7.12.1 @@ -22675,13 +22663,13 @@ __metadata: "@babel/preset-react": ^7.12.1 "@babel/runtime": ^7.12.5 "@devexpress/bin-v8-flags-filter": ^1.3.0 + "@devexpress/callsite-record": ^4.1.6 "@types/node": ^12.20.10 async-exit-hook: ^1.1.2 babel-plugin-module-resolver: ^5.0.0 babel-plugin-syntax-trailing-function-commas: ^6.22.0 bowser: ^2.8.1 callsite: ^1.0.0 - callsite-record: ^4.0.0 chai: 4.3.4 chalk: ^2.3.0 chrome-remote-interface: ^0.32.2 @@ -22696,7 +22684,7 @@ __metadata: email-validator: ^2.0.4 emittery: ^0.4.1 endpoint-utils: ^1.0.2 - error-stack-parser: ^1.3.6 + error-stack-parser: ^2.1.4 execa: ^4.0.3 get-os-info: ^1.0.2 globby: ^11.0.4 @@ -22739,7 +22727,7 @@ __metadata: source-map-support: ^0.5.16 strip-bom: ^2.0.0 testcafe-browser-tools: 2.0.26 - testcafe-hammerhead: 31.4.11 + testcafe-hammerhead: 31.4.15 testcafe-legacy-api: 5.1.6 testcafe-reporter-json: ^2.1.0 testcafe-reporter-list: ^2.2.0 @@ -22756,7 +22744,7 @@ __metadata: url-to-options: ^2.0.0 bin: testcafe: bin/testcafe-with-v8-flag-filter.js - checksum: 668b368f0c1b17cceb0db85a84217c5d681014fa05ffdf167a40fed5fe34c70d3aac91659bee28f7360d7a1c24082b3ca4872e7742c7ef29610cb688c71fc3e0 + checksum: 9b6ad78f3f93c7660d5bb1cecb341b18cf94b00c755fb0710b937e8b92d3a320440083d68f440e065d974fbb91f1be25ef41666d69acc2058e854db88100e63c languageName: node linkType: hard From 0c575c2409d1fda89037112e4bb3063d68b63ef4 Mon Sep 17 00:00:00 2001 From: Arik Smith Date: Mon, 28 Aug 2023 13:35:14 -0400 Subject: [PATCH 226/251] add ali dependency to app package --- packages/app/package.json | 1 + packages/app/src/App.ts | 6 ++++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/app/package.json b/packages/app/package.json index 89ae03920..ec36da566 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -40,6 +40,7 @@ "lint:fix": "eslint '*/**/*.ts' --quiet --fix" }, "dependencies": { + "@cloud-carbon-footprint/ali": "^0.1.1", "@cloud-carbon-footprint/aws": "^0.14.4", "@cloud-carbon-footprint/azure": "^1.4.0", "@cloud-carbon-footprint/common": "^1.12.0", diff --git a/packages/app/src/App.ts b/packages/app/src/App.ts index abde62c7b..d82c0a6ad 100644 --- a/packages/app/src/App.ts +++ b/packages/app/src/App.ts @@ -25,13 +25,15 @@ import { AWSAccount, } from '@cloud-carbon-footprint/aws' import { GCPAccount, getGCPEmissionsFactors } from '@cloud-carbon-footprint/gcp' -import { AliAccount } from '@cloud-carbon-footprint/ali' +import { + ALI_EMISSIONS_FACTORS_METRIC_TON_PER_KWH, + AliAccount, +} from '@cloud-carbon-footprint/ali' import { OnPremise } from '@cloud-carbon-footprint/on-premise' import cache from './Cache' import { EstimationRequest, RecommendationRequest } from './CreateValidRequest' import { includeCloudProviders } from './common/helpers' -import { ALI_EMISSIONS_FACTORS_METRIC_TON_PER_KWH } from '@cloud-carbon-footprint/ali' export const recommendationsMockPath = 'recommendations.mock.json' From faa4e7ed6b808a0de4b6bf72c9bf2df35d51599f Mon Sep 17 00:00:00 2001 From: Arik Smith Date: Mon, 28 Aug 2023 13:35:14 -0400 Subject: [PATCH 227/251] add ali dependency to app package --- yarn.lock | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/yarn.lock b/yarn.lock index 4083e4765..3c17e5704 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2355,7 +2355,7 @@ __metadata: languageName: node linkType: hard -"@cloud-carbon-footprint/ali@workspace:packages/ali": +"@cloud-carbon-footprint/ali@^0.1.1, @cloud-carbon-footprint/ali@workspace:packages/ali": version: 0.0.0-use.local resolution: "@cloud-carbon-footprint/ali@workspace:packages/ali" dependencies: @@ -2436,6 +2436,7 @@ __metadata: version: 0.0.0-use.local resolution: "@cloud-carbon-footprint/app@workspace:packages/app" dependencies: + "@cloud-carbon-footprint/ali": ^0.1.1 "@cloud-carbon-footprint/aws": ^0.14.4 "@cloud-carbon-footprint/azure": ^1.4.0 "@cloud-carbon-footprint/common": ^1.12.0 From a26ac926f9b746ee5e24ada9b0f95ec3c7c7e3af Mon Sep 17 00:00:00 2001 From: Arik Smith Date: Mon, 28 Aug 2023 15:09:07 -0400 Subject: [PATCH 228/251] temporarily remove checks for implicit any --- packages/app/tsconfig.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/app/tsconfig.json b/packages/app/tsconfig.json index 189b43d53..c83a66ddc 100644 --- a/packages/app/tsconfig.json +++ b/packages/app/tsconfig.json @@ -4,7 +4,8 @@ "rootDir": "./src", "baseUrl": ".", "outDir": "./dist", - "skipLibCheck": true + "skipLibCheck": true, + "noImplicitAny": false, // <-- Added because AliCloud SDK does not support implicit any. TODO: Find a workaround for this }, "include": ["./src"] } From 47122675372de64bb40a33553292189a43282b97 Mon Sep 17 00:00:00 2001 From: Arik Smith Date: Mon, 28 Aug 2023 15:09:07 -0400 Subject: [PATCH 229/251] temporarily remove checks for implicit any --- packages/api/tsconfig.json | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/api/tsconfig.json b/packages/api/tsconfig.json index dd24e23d0..cdcd0f70d 100644 --- a/packages/api/tsconfig.json +++ b/packages/api/tsconfig.json @@ -7,4 +7,5 @@ "skipLibCheck": true, }, "include": ["./src"], + "noImplicitAny": false, // <-- Added because AliCloud SDK does not support implicit any. TODO: Find a workaround for this } From 85508c30115f80247baef743fe569e25469e8337 Mon Sep 17 00:00:00 2001 From: Arik Smith Date: Mon, 28 Aug 2023 15:47:29 -0400 Subject: [PATCH 230/251] disable no implicit any at the root --- packages/ali/tsconfig.json | 3 +-- packages/api/tsconfig.json | 1 - packages/app/tsconfig.json | 3 +-- tsconfig.json | 3 +-- 4 files changed, 3 insertions(+), 7 deletions(-) diff --git a/packages/ali/tsconfig.json b/packages/ali/tsconfig.json index b0bde9107..bbf7de33c 100644 --- a/packages/ali/tsconfig.json +++ b/packages/ali/tsconfig.json @@ -4,7 +4,6 @@ "rootDir": "./src", "baseUrl": ".", "outDir": "./dist", - "noImplicitAny": false // <-- Added because AliCloud SDK does not support implicit any. TODO: Find a workaround for this - }, + }, "include": ["./src"], } diff --git a/packages/api/tsconfig.json b/packages/api/tsconfig.json index cdcd0f70d..dd24e23d0 100644 --- a/packages/api/tsconfig.json +++ b/packages/api/tsconfig.json @@ -7,5 +7,4 @@ "skipLibCheck": true, }, "include": ["./src"], - "noImplicitAny": false, // <-- Added because AliCloud SDK does not support implicit any. TODO: Find a workaround for this } diff --git a/packages/app/tsconfig.json b/packages/app/tsconfig.json index c83a66ddc..dd24e23d0 100644 --- a/packages/app/tsconfig.json +++ b/packages/app/tsconfig.json @@ -5,7 +5,6 @@ "baseUrl": ".", "outDir": "./dist", "skipLibCheck": true, - "noImplicitAny": false, // <-- Added because AliCloud SDK does not support implicit any. TODO: Find a workaround for this }, - "include": ["./src"] + "include": ["./src"], } diff --git a/tsconfig.json b/tsconfig.json index fdff0d2b3..e42980191 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,7 +1,6 @@ { "compilerOptions": { "module": "commonjs", - "noImplicitAny": true, "removeComments": true, "preserveConstEnums": true, "sourceMap": true, @@ -18,5 +17,5 @@ "declaration": true, }, "include": ["./src"], - "exclude": ["node_modules", "scripts", "**/__tests__/**" , "**/*.test.*",] + "exclude": ["./node_modules", "scripts", "**/__tests__/**" , "**/*.test.*",] } From 7630768d7aeb4e756ab38f67189f3e9ba1ce0f9c Mon Sep 17 00:00:00 2001 From: Cam Casher Date: Fri, 4 Aug 2023 09:57:01 -0400 Subject: [PATCH 231/251] patch bump for common package --- .changeset/gold-panthers-cough.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/gold-panthers-cough.md diff --git a/.changeset/gold-panthers-cough.md b/.changeset/gold-panthers-cough.md new file mode 100644 index 000000000..a3789a589 --- /dev/null +++ b/.changeset/gold-panthers-cough.md @@ -0,0 +1,5 @@ +--- +'@cloud-carbon-footprint/common': patch +--- + +updates default for include estimates config From 9dd4dfd12f1fd2765257ff3b2557f5c1766c0cb7 Mon Sep 17 00:00:00 2001 From: Cam Casher Date: Fri, 1 Sep 2023 10:20:29 -0600 Subject: [PATCH 232/251] =?UTF-8?q?microsite:=20adds=20ariks=20blog=20post?= =?UTF-8?q?=20=E2=9C=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .talismanrc | 2 + microsite/blog/2023-08-23-ccf-on-vm.md | 152 ++++++++++++++++++++- microsite/static/img/ccf_vm_blog_img_1.png | Bin 0 -> 36380 bytes microsite/static/img/ccf_vm_blog_img_2.png | Bin 0 -> 195381 bytes microsite/static/img/ccf_vm_blog_img_3.png | Bin 0 -> 51741 bytes microsite/static/img/ccf_vm_blog_img_4.png | Bin 0 -> 29439 bytes 6 files changed, 150 insertions(+), 4 deletions(-) create mode 100644 microsite/static/img/ccf_vm_blog_img_1.png create mode 100644 microsite/static/img/ccf_vm_blog_img_2.png create mode 100644 microsite/static/img/ccf_vm_blog_img_3.png create mode 100644 microsite/static/img/ccf_vm_blog_img_4.png diff --git a/.talismanrc b/.talismanrc index 336fdb727..4fe3497c6 100644 --- a/.talismanrc +++ b/.talismanrc @@ -97,6 +97,8 @@ fileignoreconfig: checksum: bd3ecff05d1290b7f7d0a4ba4267c3f34037b52aa82f13c31e35ca8803103b9b - filename: docker/nginx.conf checksum: a4632d785a11f199217d4229a40eba5a38180d9945d96799dc9b5efc6a3bc06e +- filename: microsite/blog/2023-08-23-ccf-on-vm.md + checksum: 84e94d40daf06c50f7b0e26d8d73a300d967279a87612e991e417530bfb8774c - filename: microsite/docs/ClassifyingUsageTypes.md checksum: f4b232851337efec90c4a88271cdf4cde2a9d732f0f69824223f1dcf7c6b8aaa - filename: microsite/docs/ConfigurationOptions/PerformanceConsiderations.md diff --git a/microsite/blog/2023-08-23-ccf-on-vm.md b/microsite/blog/2023-08-23-ccf-on-vm.md index 26a0c3eb1..3c685b5bf 100644 --- a/microsite/blog/2023-08-23-ccf-on-vm.md +++ b/microsite/blog/2023-08-23-ccf-on-vm.md @@ -1,11 +1,155 @@ --- slug: ccf-on-vm -title: Running CCF in an AWS EC2 Instance +title: Running and Deploying CCF in a Virtual Machine authors: asmith tags: [thoughtworks, deployment] --- -Are you looking to run Cloud Carbon Footprint (CCF) on an AWS EC2 instance? Look no further! This page provides step-by-step instructions on how to launch and connect to the instance, configure Node on the instance, and set up the CCF app. Keep reading to learn how to easily run CCF on your AWS EC2 instance. +Since the launch of Cloud Carbon Footprint (CCF), our team always aimed to maintain and develop flexibility in both how the tool is used and the options for its deployment. With such a large variety of infrastructure across organizations, we strive to balance supporting as many environments out of the box as possible while also preserving the customization options you may need. - -End of preview. Rest of the content will go here. \ No newline at end of file +One of the most popular options for both piloting a CCF instance as a proof of concept as well as for an internal production environment is to run CCF within a virtual machine. After all, it is a great way to get your feet wet with CCF without bringing extra dependencies on your local machine, tying up any resources, and enabling options for an always-on service that expands beyond your local machine. Alternatively, you may want to get creative by customizing or automating how CCF fits into your use cases. For example, perhaps you may want to set up a cloud function that automatically fetches cloud estimates for every recent day, week, or month? Or maybe you want a fully accessible CCF API for members of your organization or team to query? If any of these benefits sound appealing then the VM option might be for you, and you’re in the right place! + +Virtual machines come in many shapes and sizes across different cloud provider platforms, including AWS EC2, Google Cloud Compute Engine, and Azure Virtual Machine services. For the sake of this article, we’ll be focusing on how to create a CCF application running on an AWS EC2 instance. However, depending on the chosen operating system or distribution of your machine, the steps should be relatively the same! + +## Creating your EC2 Instance +CCF at its core is a Node application running Express.JS for its API and React for its client. So once you have the environment setup for one endpoint of the application, you’ll be able to run instances of CCF’s API, CLI, and Client on the same machine, and switch between them if desired. Before we do that, let’s create our machine and set up our environment. + +To start, we assume that you already have the following: +- An existing AWS account with permissions for creating and managing EC2 instances +- Followed the steps for setting up billing data for your [cloud provider](https://www.cloudcarbonfootprint.org/docs/aws) (i.e. AWS steps 1-3) +- Basic familiarity with navigating the AWS console + +Let’s navigate to the EC2 dashboard and select the option to launch a new instance using the shiny “Launch Instances” button: + +![Launch Instances Button](../static/img/ccf_vm_blog_img_1.png) + +From this point, we’ll be selecting the configuration options for our new machine. While the free tier may be tempting, we’ll go with a t2.medium. I’ve found that on smaller instances such as a t2.micro, the limited hardware can sometimes cause issues when installing node modules or running the app. So we could use the extra “oomph”. However, for the sake of your own instance, please consider the following: +- If you’re expecting a large amount of estimates, consider a larger instance with higher memory and compute power. +- If you plan on running a [MongoDB instance](https://www.cloudcarbonfootprint.org/docs/data-persistence-and-caching#mongodb-storage) on the same machine and persisting a large amount of estimates, consider increasing the storage capacity of your instance. +- If costs and efficiency is top of mind, consider choosing the option for an ARM instance as you will not be able to migrate from a non-ARM instance afterwards. +- Running EC2 instances incur costs! So make sure to stop/delete instances when done with them and that you choose a capable instance that fits within your budget. + +![Launch Instances Config](../static/img/ccf_vm_blog_img_2.png) + + +You may also notice that we’ll be going with an Ubuntu image as our operating system – a popular and widely accepted distribution that you can use with any VM host. You’re welcome to choose a different Linux-based operating system such as Amazon Linux in the case of an EC2 instance. Amazon Linux serves as a Linux distribution optimized for running in AWS. It is also optimized for running most Linux-based software making the steps you’ll follow almost identical. For the sake of keeping this tutorial a little more friendly for other cloud VM services, we’ll be sticking with Ubuntu. + +After making some final decisions in creating a key pair (required for connecting via an SSH client) and choosing a security group, we’re going to hit the even more shiny “Launch Instance” button. After a short wait, you should see a notification that the instance has been created and is running. So let’s connect to it! + +*Side Note*: If you’re more comfortable with the [cloud provider CLI](https://aws.amazon.com/cli/) or other ways of configuring resources, these steps can be replicated using those methods as well. + +## Setting Up Your CCF Instance +Connect to your instance using your preferred method – whether it be in the cloud provider console or through a local terminal via SSH. Once you’re in, we will need to configure NPM and Node so that our server can support CCF’s code. + +### Installing Node.JS +We’ll be following the officially recommended steps for [setting up Node.JS on an EC2 Instance](https://docs.aws.amazon.com/sdk-for-javascript/v2/developer-guide/setting-up-node-on-ec2-instance.html). Henceforth, we will be relying on NVM to make this an easy process! [NVM](https://www.bing.com/search?pglt=641&q=nvm&cvid=ef35fe5448b345eba7740e8aae9b0b8e&aqs=edge..69i57j0l5j69i61l3.383j0j1&FORM=ANNTA1&PC=U531) will manage our versions of Node for us, making migrating or downgrading easier. + +```console +curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.5/install.sh | bash +``` +We then need to activate NVM by running the following command: + +```console +. ~/.nvm/nvm.sh +``` + +You can verify that it is active by running `nvm -v` in which you should see an output of `0.39.5` or similar. + +CCF requires NodeJS 16 or later. While I prefer 18 since it is the current LTS release, you will need to run the following command – replacing the number with the version that you wish to install: + +```console +nvm install 18 +``` + +*Note*: Make sure to check the latest version compatibility that your image supports. For example, Amazon Linux 2 does not support Node 18 at the time of writing this article. + +NVM will also inform you that it is setting the current version as the default. This is useful for whenever you need to switch node versions or have node disabled for some reason, as you can use `nvm use –default` to re-enable it. You can verify that Node is successfully installed by running `node -v`, in which you should see an output of the version that you installed. + +### Installing Yarn + +While installing node also automatically installs npm, CCF uses [Yarn](https://v3.yarnpkg.com/) as its package manager. Fortunately, Node 16 and up make it super easy to install thanks to its inclusion of [corepack](https://github.com/nodejs/corepack). + +To install Yarn, we simply need to enable corepack using the following command: + +```console +corepack enable +``` + +With a little magic, you can now use `yarn -v` to verify that yarn is now installed and enabled in your server. You’ll see that version 1 (yarn classic) is enabled. If you’ve been reading the CCF documentation, you may notice that it requires Yarn 3. Do not fear, CCF will take care of the upgrade during its installation. + +Now for the fun part! + +### Installing CCF + +There are multiple ways to install CCF, as noted in the [Getting Started](https://www.cloudcarbonfootprint.org/docs/getting-started) section. While you can clone the app and get full access to all of its packages and latest features as soon as they are available, we’re going to go with the [Create App](https://www.cloudcarbonfootprint.org/docs/create-app) option as the more stable and simple solution. + +To get started, we will run the create-app command with an additional flag to note that we wish to skip the `yarn install` step. This gives us flexibility in case we wish to connect our data using the [guided install](https://www.cloudcarbonfootprint.org/docs/getting-started#guided-install) process, which will end up doing the installation step for us.: + +```console +npx @cloud-carbon-footprint/create-app --skip-install +``` + +You’ll be asked to install the latest version of the `create-app` package, in which you can reply “yes”. + +![Name Create App](../static/img/ccf_vm_blog_img_3.png) + +When prompted for a name, feel free to choose whichever name you prefer your app to have. In this example we’ll be going `my-ccf-app`. Take note that this will also be the name of the directory that the app will be created in. So you may want to make sure the name is unique and matches any folder naming conventions you may have. + +Afterwards, you’ll see that the script takes care of creating your files and moving them to a directory named after your app. Your app will be successfully created! + +![Create App Successful](../static/img/ccf_vm_blog_img_4.png) + +Use `cd my-ccf-app` to switch to the directory of your app. You should see the following contents within your directory: + +```console +lerna.json package.json packages tsconfig.json +``` + +*Note*: If you run `yarn -v` again while in the directory, you’ll see that the version has automatically updated to 3.1.1 like magic. ✨ + +At this moment, you can either connect your data by manually creating a `.env` file in either your `packages/api` or `packages/cli` directory based on the `.env.template` files in those same directories. Make sure to check out the documentation on how to connect data for your chosen cloud provider: +- [AWS](https://www.cloudcarbonfootprint.org/docs/aws) +- [Google Cloud](https://www.cloudcarbonfootprint.org/docs/gcp) +- [Azure](https://www.cloudcarbonfootprint.org/docs/azure) + +Alternatively, you can use the guided installation method mentioned above to run a friendly CLI program that will walk you through setting up your credentials and will create the `.env` files for you! + +If you’d rather skip connecting your data altogether, you can also [run with mock data](https://www.cloudcarbonfootprint.org/docs/run-with-mocked-data) instead and move on to the next step. + +## Running Your App + +After following the setup method of your choice, let’s top things off by doing a `yarn install`. Once the dependencies are done installing, we’re good to [start our app](https://www.cloudcarbonfootprint.org/docs/getting-started#starting-the-app)! + +You can now use `yarn start` to concurrently start both the Client (react dashboard) and the API (express app). +- If running the client or with mock data, your CCF Dashboard will be available at port 3000 of your instance. You can view the dashboard by navigating to the public IP of your instance followed by the corresponding port. + - Please note, you will need to configure your security or network settings to make this port available +- You can also use `yarn start-api` instead to only [run the API](https://www.cloudcarbonfootprint.org/docs/running-the-api). You can verify that the API is running by making a request to one of the endpoints on port 4000 of the instance. + - For example, try making the following command in another terminal instance: +
+ `curl http://[your-ip]:4000/api/regions/emissions-factors` + - Please note, you will need to configure your security or network settings to make this port available if attempting to make requests outside of the instance. +- You can also use yarn `start-cli` for [running the CLI](https://www.cloudcarbonfootprint.org/docs/running-the-cli) and requesting estimates directly within the terminal. + +Congratulations! You now have successfully created a CCF app running in a virtual machine. + +You may notice that if you exit the SSH or terminal session, that the running process will not persist. In this case, you can use a tool such as a [Screen](https://www.gnu.org/software/screen/) to create an uninterruptible terminal session to run your app in. To do so, try running the following command: + +```console +screen -S ccf +``` + +This will create a new Screen session called “ccf”. From here, you can run one of the `yarn start` commands to run your app and then use `ctrl+a` `ctrl+d` command keys to detach from the session. The session will stay on in the background and your CCF app will stay running! + +You can always reattach to the session by entering `screen -r` in your terminal. + +If you’re more comfortable and don’t like the idea of having terminal sessions running in the background, you can also create a `.service` file to [run your application as a background service](https://stackoverflow.com/questions/4018154/how-do-i-run-a-node-js-app-as-a-background-service) instead. + +## What Now? +Now that you have an always-running CCF instance on a cloud-based virtual machine, you can now continue to explore both realtime and historical estimates for all of your services in the cloud. If you’d like to explore additional ways to enhance your CCF app, consider the following: +- [Running the CCF App with Docker](https://www.cloudcarbonfootprint.org/docs/run-with-docker) +- Creating a cron-triggered cloud function to automatically fetch new estimates +- Configuring a MongoDB instance to [persist new and historical estimate data](https://www.cloudcarbonfootprint.org/docs/data-persistence-and-caching#mongodb-storage) +- Using the CLI app to [seed data into the configured cache option](https://www.cloudcarbonfootprint.org/docs/data-persistence-and-caching#seeding-cache-file) for your instance +- Creating an internal dashboard for your team or organization to view estimate data + +Hopefully you’ve found this walkthrough helpful and see that this is only the beginning of your cloud carbon footprint journey and taking steps to help create a greener cloud! For more walkthroughs and technical deep dives, make sure to keep following the [CCF Blog](http://cloudcarbonfootprint.org/blog) and share your experience on our [discussions page](https://github.com/cloud-carbon-footprint/cloud-carbon-footprint/discussions)! \ No newline at end of file diff --git a/microsite/static/img/ccf_vm_blog_img_1.png b/microsite/static/img/ccf_vm_blog_img_1.png new file mode 100644 index 0000000000000000000000000000000000000000..843217db4c06880ac3ee2229ad207c0476d0039a GIT binary patch literal 36380 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 P$rE$1bv{#OgSqn`cDs*R literal 0 HcmV?d00001 diff --git a/microsite/static/img/ccf_vm_blog_img_2.png b/microsite/static/img/ccf_vm_blog_img_2.png new file mode 100644 index 0000000000000000000000000000000000000000..3e395a69510be23f2f882ffb14d4dd3f8d667eaf GIT binary patch literal 195381 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

MNeU%S*i)VI0R9q9{f_95tkHlblHf$m5%T2 z#iHc*@vLI^8mv#jfNEWtNGNF3eln+Ckyxb)0wUjed0swT+84K+S04I<{Lr1j3C_)N zVzuXd+zNZ-5G3jMN6$;2PKm*V-hz5W%bZYs`s*1V#u9NuO`dcfm_4QLR8f4BMxxvJ z?=kDs`wVclMLVC(dI1f47rsMO0|Fj(E zZzD7{V~=-3eze6yWj1w~7_9?pdOv9H8lnqi9pYemv9m+f+?~kvV9ucAr&m*Tiz9X; zR0+{wRA9_(Ng{5*qdxsUO&zzwVCIRV$c_wQR`sZkZ>f)W3q1Uc2(d4CfI3$-6F`rn z1JybeZuzCBvFq&ytkrfKBNWJ%5K~`kn}3Lugo1}6dYw2mD+`yH__#bN!f}e<^XHa+ zOgiGX*sPRkk{Ht?q`3X${!AbO-)VMcmN}@M9#_cPjtPExy33*ITq_5EBUqhnIw3Syi-PMOxErE@` zv;+NoAv!xh?(29#xM)Q^&})@Crgi2%N_#sp){Vtq;}%XEV(B=XCmeCR$e$|YW|n>1 zT%P&U^)Cnb(AUa~ZsW)1kRI-U4A9dZmQEd>%Nc-pkk}&2DsrAnnosHm*>PFNY}B6n zqJ7GzC~O8@+L13Z_5XE|W8|l{N94EWp_{yYJ_SY13FMaKFbFE$GCoD#gV!oa<)XyD za=m|XzImu3KDL>6T}X^oX8xTD{p+-r0N}~A1!^iO{+7=lv+;ONw8_XB(26NNnbCDY zi?d+&&tFPTAWOLN-vdtEBrDzlm$l)6Z21Z+TbqY}wSud)3loKQXwmaj{!6)O<^Lj+ za|1oJ5!}PX5&!nPD$t1yd6{Z#!((n=K5<&Z`Vcig@sD!QvIEb9wH$ZR6$m6=YI&S4 zF>sIF2>i^HT~o*?biLdpe9M3MPJU^jkYxV*T4wlwyw^-OTP^Y*<=SK7KzFOe zW_S1ZNBzKH_PTvR4#xXJW75*d)YYr*ITmU6jMc#^t7UWzg!iD>lN71l6<`WlDzjf;IzP!%8NL-IBqsVM7><}%cNA@icjH7NU$A45 z=@g@tm9(f`+&D9I-gp6eLa24xF$~XJ)}6_1+oxO#hr)^ksOYn@vc49Pp>(}5!_C%O$#`Ovwr9Aa9yunlTd#Yl|FG&v zns77Glvy|S7}WPvlP&t@20m!?qUHL7*)l>DC*zq3{^Dk;N9J?(YcWc|@sX=|9TVx) zo89~a@lg0)?UDN~UHyZ&os9UrC>nU#@;*OX5#GG!vRCaqn3Suasu&vYjeU5_N zWiy5ZzyhIkg7>9W_EDqmr_D9=+qD z8F`*=?P;oYH`W9b@a5?YAETjS)4wvM4 z-W|%6eB@{}TKLX8Uua#=?$}5vj}Rx{Z)3Z7?-Fp>?SueeHcv__UcylKhdgPJ-5v;U zVEZBe>ubsC;Z6`y!^4^$yN5Hy-2niV;6!?}IDiDq^_nhvY*0FVw=h{(g!6T`)BQXy zR}g}-NMU9_u z2~D$5FR+b_c}#SxvaL_U6oe3;f;MxeMT>(yqdH&rB0tcRe^m#=)t$qo$-x^rX+Un5)Uf0p^z!6N}O1 zx{evo;?_)})h4T4ZHN^PPs)p$@<((HLlDCfSBfW--fJ@#6@Poa+v{|{kgr_Get#ut zFt{OMWJGo)8x=(0rcO;w?Y$<8z$aHM_98@*pSga!@WvOS;VY6(r=_G6-Bay)#e`hH zF_|?><@L_hny=1|0H=%uF0E9(I1y1bnvPQ5wqA*1e!5#DF+DvY)ntO+^NwE~?piFC zFmTIlZ)H$7=zHBwp7y~F2B#+I*UZ9L765N9qszK8>k9rM$zW9((dOH|nC;UhZ(Uya zZ)zBHFrc*4SO-$F^WgpO^~gwJ5A(tvZjEHACTlIZ4OYJtSd17LY9%r>8bZ+V@vRFg z=e`2EnG)~MR>ZT{J#$7zA=p>Hw6YK7rmm1BPB*#wLo#&nnbcObekZxM(Ar~lAFATs z#zzFcUVzgm_N}uzGn=UA_s2lYhcsO&s5Kid>;9@f^O+ztlhawjQhR4mTJK&}mgrb? zQv>4ZwtmX+U@{XK3;D}kE`#|}j&J_}i~l{1EUU#D2{x-mK&XB$3L2V86GLt7&nj(f z(~aDY?O5Fyx?L^+LX`}cqhYooTQ)uPHc*1gU(l}~Wa;*BS+U7tnT$%MB4KT9HXV)Q zjz?aHass%0uX?dDhQHR=j-Zn7{0xr@7YlqOUvOu)yB6n)f+F_e;+Pr^*waYdASr<$ z5==0MmQXLPx@}HVMfG1(`w%h)v=KzT?YN9*`dq4drK|r;S*Co@~M6q-mz;- zr73ZIJdW51bSQ+ls+|Pmuml7Y$QBedbW#%l!H;gzlb4@w_!5;lUq838rJqYbz?r)r zTv}QxritG_dC*{6uIq68DJ?`34;c}WAo~4Swa)2GBju~tm-XRd*xl++07zr2N|hFz z$_!Sf$@-cz9%V zJhn(FKgGVxAb8s>w7s~_$3N3M!}{IBjM770KOkFAxgi59sK6g!cV^*jAM8-}gosLE z4EYhtCI5Cg$!=rE|8_a3K<9~hP9eMdX|cpc1J{Yu67zj{Uvy)#X z&3aDw`DtkoShV4C`17F0(2x3hdzFai8%&uBG(Ra0Owg}#RvA)NsWYV@(#8~yC7D0- zbhw+QUR_-&W6>4of4lT;gBKVR^NOjUYHeT;>la zKzjn5KWbigC!Zmnl8M*H|c>&?Q-An{Mc)=Jx3Du{?bRU-|zVL;52D`Mn)w;MS1xsPc0n)dmnOq)QRhK$P5=9xC~o~>FCG^ka!q-V8Qe^!Uqlc zlR<8c2+3J7v(3W{B9i{bjc`jNEsk>nN<&YK7W@be8aJGR{qRYCJH2Z$MigJ(p>pDj zrVk#au<+SOIJ1?a-oE<_knBRQtop%}J!>Zhj>X+%$;TI1Hy^L;=j(3s?$ ztz#HCFbVO_ebdU7TH+AEdh&qu^~D1@W`}90(eddJScb-FXp?Q$iiv2syOiu~=!T~h zi8gKg(->ibx@UG#s$9kB(FDxgTMBl=KO2yeK~aR7bJD8t{!2+CAo-%o?7rF(&ObKo zo(PQAwE#j$IZnji^c`_&xGK{{7*uqGdWBhP!nOY&w%5ijxm&#CT2 zziv_^zQiR8@pB3pSu;s0X+<9Ags6m3r!w?)uSf7+UGw-%b;!}{!KNBGUkxbIN!II z(Zr5>FZInv0XScKqDw#{013MB-WARdDi(~VHU;;E6{CN}p#j}c6>Tq%kDE67tH|E` z-Rg#dIYEe*xBd0bft&~R%Y{MU;s04M`d60ld--S?U}79u9Zs|Utpopk8I}M*lz|2O zvyS0UYU|q;>2r2dw)$q=$Od{03<%DT1?bdq|JNE(&LpuOJlnAcx(3L<0(S%A?wc+< zdwZjYb5JOFD1c>N5#zQWx2@H0eueX;f(NWA0Wf?T{{IZgDMAvHl}+~kIAB+AcItq| zw{2_)iALAe-AJPyy#CEKK!pPs)mY%4IosY^_x-;Zh2PnZ89FeRv&m@+1F}4?*61kJ zCfw@!VqE^lE6$dlMJ)!9BQMwJ=X2f7DaOUb*u>_xeO->X=y+O4w;M}XHGD>A>J;g;v%fLC z*{6w%jnyx=Kre=&{+ILn&uUQ|ft>O1Z`(Sa%_ghkeg@~dJyN{8*&_%oC(vu<3bVU6 z9GfqpCHB39l!}%TDUL}rzF*cVVvqb|baa%NyUQFv%&hpyc^h?y1CWW_bxX?VLaEUuzL){Yc7Je@08XdHcB_iac9P>j!KHd|9eemIY9XmBu{ zZ)*fFhmTfibCQy>XoJHZk`o6Zpv%h2&KJ<=v(%z zj?@{AvB*FT>kLKV8qSr-Bje$beY_lgONb@Qn!#Ng>DIkBz6+lhUz?lF795qtR-SK)K%!U+oO(yWsDS zQUy$f3lo$x5WTHqTFg*Eo8$Y2Bn^jVxI#-W=zd{=V?l9#j&b_+pBc9q#_z)j{DJed z6K0yi(*N7aLH(X;NN{HM2H5>!$<5L24?Utk^Np(&`9_1mk9eoJ$zx+f0)`J+{nfG9an*ot zFj}Tup7X0L=8UsRP*XL}@Ot@!iw@9Rpfs@<{PCg$2uG>uIxq3nJH9u*?z2UQM@L5^ zPZnptCo`L16*hW?_L@Jt={>3j>L~Hn+1I zzZxr!-eZK4Hd%LSoty`(kg;7=@MwXc+i}YACBduqH^s|eE~z`w=hKESAmf;QJ6KK4^7slt6nd=>VUW&1#su=%Z zcu8^F+|#vMX*{pPgJ~DI_|qNNI{QW1kJ6Pan8AYazKT^`&xnnAUg;Q6lO#SI9saCzaH*G81qMX7xH@eu_4&U za+Uxz&?J+_jI>f|@$`nDo$%wlUGq1QFDQTh-8$!oa4H^vryT5TrP`>cx3COswZ=BQ zpwM`cr%bByjND7%LCi^D-(kvL!4k%uPnBtF;VG2@cetutM_@8K~n7LR*b=Qo$snB~- z#qE#R*fv2|O(@AplIbdyvJBN1iw~=;W9s)_zhnJzrK-R z7epP!#VgLO)!H9SO8Ar%zI+LcSBS~Re}4@`y%WXj>ESoicm%_-_qe?xk4x93SXN(F zP>T8ZIL(CcQJ!xM`p|c~=s?#y#4Yd_tCv%bK#iP9DPeFWV;D?GPPw;WkdMc3C;viZ z01->n@xVAv-|?`%(RIHUfzFs9KRRC1wC*%nwb5$7{Go1MZHa-xL=g;g(GNDmLB2gL zILg>+iyvoxSk>}vRfX$%N%sm_oAsjO0dG8u2|KIdI+IqJjd{lyzknb%ue`MuEbw$n zS^T}E#9F}2i>0YJS>OIUX&vR<@mEA|Sz|6|Pu{PbS(}osZ9%u~$a26Ed{7iXq&c7o z-NMp29qhKwz(0+Et(F9xZEXiEn`sM%A-m7H-6NlL6XZ2Cu!!+_M08K_S{%0o9_1=9 zblG2e$z3*Bf1tad0U!C*oMACtLaOlb4Crnu*hZGGnvz@P@UmFHg6_s;pwr{Cr z7P9bE|B;9fKs>)<`I3=zb<6dTzEmCp_VB1`F(|49Wl^LEjV$c!>}lC%=V;6EHUD9D zw7n>>MHoauX&o?F8)k9QD!y&2+BiU+cedJ$ARy=mm|0FJsJkhmDk?#?_*V zEQ$z@w|2vN^R$@dAJq(GkDEa=D{(2VwV+nE%9cy=AC)3Oi}jSVhAoRVsQ6GMMqE1!EAJ>tSBkbi=X%&xmuI!zj<=tFN*ITQI*=9=4d&og-;%f zFHAwJTbof8QfZSX7jNrKEkh_w3}W5#-sx3MMDA3{G?laog2etRGgvCDPV@ zzsGh;iTHo##&1ddHBrKUO9R<*s&5Q^9ae{Mfq3y-l>?Kpl%S0msE}d*(9pO0Q+2hM zdGPkGTR~WS1{5AGVp6}MM1Bg7BfG=xZHvM4qa)w$ZarwElX`RkQ)xR}CstnyubcE> zITfA2jgNN*uKRwbh@6%ogYP;6nKt*Q<-?f%g3-0EKZx#}k@E|&z4FVBoIA(eD0f(p@U6=X6gK5r24c3suKFxZ@6 zY10|!g=p7W&B^1CKO=rdpSt4I-|mNcT#b(gu*Z%u_*%kZGEavH+PXUwVQ+`ibw=@W zy&CVLC&^{}iU4dLF^gZ`@mW{fgx|sbyAnAxjVc>vu(U&Kix`Gy_dp}W6MJ{M!O8QM zzM5+biuDCm?}v=E556`63*}#0_D7PWmn=XK8YUV8ZVEce(z)BF0lkaGQ|FxO@uV;6 zp~~X@e4_Co7~*wS5^*7)UBwua$TZS-wMbWE?i5ro9NEdmuQi@3>K2U4txoDQ&`(q- z{ViJB+8CyVUwSCZ%FCsF5Oj63n>xktWh$ngE$+0R0Ui5BmPt1ZGe(Ux2Nfi5F^oUa zsj^RJNv#5r=>>&KNJfZ(zCy#mYzfAy>*)AW8Bd4+gB{JvzTu9HbYPz557xxm5%Cmu zD3bDj8EK8pYEMDe|cWS+t0gi!QbUR*77$Z#f0}^e$y96VXjf3x z)5}pV_@zo^uz7UMS?g6guJg4~lN~{#gUC*(V8mVgT8D^JqV71+p40g}XKd>;idR&_ zN}^%_Sjun^w>w#?2a$*@N%VdJ`H^*%;+h6yIq#g8#MAIwOe|9r~s z0RaM9j*?MS}lY%kkSZ!h--1Y8?cL7jqcXA0Zpg2X?D8N>PF4?`c; z-UJN|zuU2(MR0DLSRGRzF#j_xipdCG3@6iEmrI$B)c>-`%>+5yCz{}S z=_MgQ*=l1-FOMx$#b5G46aJY^HnqrdSMw89?ID#>&qK~kpd2{AFY(VGrYWHuQcZ$0 zJBc)6zR{Qzg-}oC{)SgF$Kd!^`A%hk8kuregyw51`rbsq5ma@X?b9DArEcX zSSb!SLgubNH`GI#PW2NO&;>}~PzQ9;^gVv}10>Rx^Vn$nKZQk$2K=RyHi@coN9q6em=c?QfT`%ne`UV&=YjZ)iKqL0@Ez}@ zOROzK#wdGf7uXv0?sAda>uC_!_wLrD4qgjInmo%oGsCkClNSsoa<`kx(wBksjUqL5 z_Uyxdu>x5RrKmnry;YHEmKN8P)mGAa=7avf9zsSkr=kt%q01dH9&aOpD*KnO^wFN> z_I7`-dZFXvNf`4ks&cNcfTgSxSe>Mq?6}hcZ}{QM`wxhiI8E1u6CU+IIE$OmM9ig3 zOLIk@t|k`rhc0i();-4}HF+Ztui9M#o^{0~vhM3ICs!TL@SA;}flIPH$(=Qy6B{R% z5|w=&DF@mzS4VtKuB;^a22`6n8aU|xNH2#Nv|sOLs`04A#H3eq13RGj;6da<4?)c1 zd+iD{OL$@NJS2GSVZt0s&%`R~Vst2fKsKjW2P5u@=?qeM3|B!!0ojiSJFS)2MUfxR zB-xd)V~qZucg*F7K7jM=g#lGmwM^zj+V71uSR zqvf8XIVIzTd*$8*&Foq#s{u5ng=)23kfp+`JJ_m)TQq|=#DS}Afjzfw!0r2i<#VH~KCpAwr~ z19MYOT$ODt(D|j4y@<3R(|gkw5bU3$e7}5BJ^ks~NAT&Y#`=S98DID6iN&BDhR{0c zXsZUIdNr8k*I=WrdrD6D%!qn%2jYH_|deo9P;Ty zf7Bzye}3B>=bi))aNo}F5dNy973{*Ry5_BpV*tV(wX-tHnzfMg)VY91GcPMJBwgc^Fa$ zkk^A1*n+D@{Gprrv2CQ|9kZ430!OOh4v3dcE1u$A<=$9NU3vlxOkoxi@k^$m7rBZB z=5V8OWY1pBtH~ssh^RU!Rt(HBV@B9pClN9*b&kqluPoZ3NJ(A_-*YKG8r@${$Hz(9_>i_N=vc~zJ_+0 zfdkq*-)!bL=^tHM4Ys-w*SHC>#NVwbC`x-ewZgL6L>A;jQJ|S*MIrhXg(i&OgNp|B zg)->wzb9dr%bDq*28lfDp@B_^KzGPN=Vvp?(hSU`^%b5)sUGRSN7*;&Uz~TQ1z0xLZApt8-yo8q)<{(h{2ngaB7*(E5`@=8vjv zHh>aQqt1EVL%p&Md3S% zgv8}_Cf5jGqo-IIYFV%3(DGXy9O}&^tDoD(iYPW38_7*XE0U@gsmR#QPo?!G;`7kP zp^Wj^l$2N0oYKQTwZy9ww4>(euKd**PD;Bp2A11d6{3%a&qgjZV}L%v6Y8#r890xf zZr7?(?&nt<%29&}$S@DLjzd}q5CWcBx_^zZ;*RH|j;4B2eCym|;DO`{6)sEr_7Sjc zt}Q~kzNQ{h_!3^bQzDJSA=e$1hbq;-3XRv5DKNRG_BI6P^JY~<^ch|9w?v43OQdau zL**Zd6o3kW<7_<_eu2&-fsU68!$P9kPD>VKsO9Z~4u&b)8jkQn`4$)9Krah#Q#33_ zQ_hq}6?E=OdPk#XTZ0kr2A%^!qhn6l-}|;!L)V2R>4a)v(*oee%#3E=S*Dg!D8>eh zK>$nP99WySp_4kn4d~R@u{&-tOyfQuUc~Qjl%;kJHtl$(FGK+m=rEbL^Obr0o$H#?{QLGo5*%#E~*DjucKMfTgj0UT1TmML!W zALnc%0c;T9>MY6n{AS7x{_2X$s@NcPfp4G%WL*@#WfbA%vRdw5Ya;~eCf_|*%jfD( zGuIX#4+7vX1V!*n2*TD`2~^Rj7tqd-TRrAex-Wp25(!)=|_=k2EzwLU=vc$IN@agB$87h{suXiwQMFcd9?d=M)fq$uSV z7>3uG$d=a)&J|!&G0zzk&EO*qmw<(CD)h5HQ0P%8aUhKJ&1i_jvZwP&qJ(P?suOgE z`S?6@0&Qem(=_w_T9td@g~G-Kv6vVY6oxXxl4K?WibrNj-|$c$5b1+Tz$T6q)n!H1 zb>zg;8Jye;)6+hq$7VtSfjtAuPD6)M`r)BnwY5-D?J&EChbCbxEG$JKIQQC&EHHy5Vt&tPGn7y0Jr*m}dW3%QXL6C2?uidJ*UPZ0E!gvm9gNJd(}WZCc7 zs(X`WYahK+5S0tBciKTql<)>nk&eU2{jE9hadpm}cGL;;N*Pcp z*c($NVEh7|FB0VgSsj_q+Lz2b{`PXmS)6yJ<+I4~Pjr0`r6qPX?-kv)F>|83HlT+w zNl_5Kx7Pu}U~PhtK^Z2K7}k1ElWqmrx)E3=@vPyXjG|kiZY&mKOY*AJw^R!_4*tg$<&1rH zG0>sgozd8nK2avoCX^*_H|#cmiXUp0uJf|6G1zRb6d9xPr4`Q^3!$qmCGRIE*c1Em zes>Dh#d)-K?2&{pm`2V~ts$`+)P&VDF2?Qq6h@J;Wf&^REkU1XWZ$?d$N0OhA6|-!xz{gO36%nd^6E zX5$5wvC`?`^mMbA~KPBoZ7B_h179%}ptz(lE|Geve z?g#LcZ5fYoCJ~D|?lcQk+QE(|3*V-@eIH*oKH#055nitl5bU&BVbFe&=N^+|r$ zF2Pq0O)JzvAmH^V7Rf3Oo@qoy$u*jLBO9qmNai-LFE9C&3`uY@GcZc}U_u+Rgc;1X z_Z;{E8-QT{LQK}*8%=BoVaYOT*U0>1(f`hNyG3%w0@A9ht$l>Z5=sKk&#lPBM4X)9 zJv`c_6M7pOnCq>B(DMo07`Agi2PO$|q4cO|YKDh}Nqk$5I}C1hb=AT~3&%QVPfAR* z%p(coi!TXXz%zITlm8<$TEO-klymK;v5sUfTH-Mbp-a@71|u25_t2cPBx5wYgh;uZ z3g=QMiypY-a$_A+hPq#6MZCCfsFtssXp@k!%-(UdOqZz{xVS1Rzb`v5(9$-Y-S_~3 ztgL504*vR;={NBUCkelzKLGS*eP{^6^W`}sMVpuEPacTQ;QQ(aOzr7S3E1f}xTXej z%?nUrhkbQ&*K7>72LL2pypI&9+xO6#Ztif3JKBk|vb^@Q2VV~EFKC&w`0k$oo6TNb z_C;@>|F8}QC5lgV2Qah8MNvBiSHqXUY8r|7+ zJoixRpRU(h?HR02F=m^idEY^Gy`D&sx#}}8) z9teE;F_m>!X9ld}6MFA=55_bLWltR)VVkZF?&x&a#hu{f!QMn{3-dM?NWk>!3qOcE z6#K^*!U*$`y*sa6flzBU!H$f0Zf+e-LV4uZ;v^;}=106lv9hqFo`)Jo?k0zJg?wJt zRf=<2Pe@L_QA15jPoH2w60GagiVA0oQmgau@MwMa@ToN%F+dmu5&_PoCs9;2fd6|V4nJ}Mrb91&re+S^CXEh`(Hqx6h>E|C4H zD$WQ`Nqn%|G6z_oHj5g$B1Or+xFPh6t7~j@EXs&bA04gz96xt;{zDRjfX4UREu;Bu zV75?(*WJ7-gW(~4-;;8fP7`Q+bxCHZT?G* z$J2GwH@o(-GVn3p4;%;toP_9@^xKgR_nz54RLy1!VV`VtUR+m25mX`S?O~b4 zr}|V9x+0mR>RqzAJr8Eq#*_zz=sEJz|HIc?M#YtFU7!gN+}#Q89^BnMXpp3k;O_3O z1;Hga1oy(-9fG^NyT3}`?!NcEZ;bbcKb$(U_c?p7HP@VT6)$MmQ1<3Im9~Y=MOsNO zO>xm`_O(e1Xb`JuW$}LSW5Th`Ra2f;{O?+^0_>1IeIGt+(sI8UalV(x0TkDI-aB!r zvfH8xk2VkYFJbXo{0wdji-3u=jvkDt_*!z&G43-&iQMUGGx#*p9OvInq^WQBfvM35 zb9G}imlBixKx$f9Ci{G8jX#iANB{B?K9WX`#~x}Jgr#U~j06Vqd6%_syJG}JOx&Ba zu`l#=*4ku%MH4y5(bX%3rf_sLbZpqUgYi_V_nt;M!ZCzA>4t7-adBvU=GPzQI_G9M zzV_c*nb`b>!{{^INh(p+{9*&OD3pUE!7u8nBoUD61CeTyAr)z zzmgExHJV*vx14+Wp}7nW4!XYF%;6a?zUOncw7_s=6GY1@nJevkF4^1qMbBK=I}B6N z`p6p|9v(Gw#tP30SHFAa(?Y4*!a_vY<8M?$_5m7tQ_`BU)?tr{Tz_voEnu}al&Ij` z!Op4se4~4!-M}ZDJ?;BvsrlWcnXQ+M2bz*X*gafpJT$+#`rS=wJ4QgOUZt%g<|X~% zMPDLe!f#GPWe~MBAyyE6cy3{vwAhC-J2%QYY1rqclgB=jmKsW&1>-u&0psfDc`bvy zA2Xy$CoAl%*j~Aw=d02DrGl#PQmINNw{TsxZJqbNCwU38BAgX%=E}GlC-hzAEp#Ao zY@Gg{6c>B1{~0Q;SPfNqShEm>xCr-eWUwxZff`q5xTmN%$f0_9es|SB zf;T+-pB^dhH~}U*I0m>D>Si){&L@l0CL%RwdA9K%%>Y=>z)eOv z-l0;y_>bn*-a?c+p$Bu@gBVM*%MnhM(TQ#6EMF#SQOj3cHl{G_GCU9RlLn|T=L~!V zbhZ0aDhw<*G^h{0fCZKPQAAO51|E<~u@ARId`oqU3o%ZbHwMIhVBd z#PR9p|kMV9*tDiNO zmY++@L+SOkwc$8z7W}Ux<(syCWC!&xH%33J!oz5Ld=e&Oo)%z@AVVx;Oy+TbHi}Y1 zo}&tT$_@!3kThU}_)~Xm#Vc#f%ljP)1BXhrNx^XgYu)%@BA@!n4}v;{DHyAk|D>mU z#Y4V!x2Z{|gpb-SU;6kP0w)!o&VB(^xu<|TcWRxEjDT1Vk=8$603K61E_icFqg)ml zLkF*W0}jzpE5Fk5ullJk1m zTT1{`IhdBY{%$I|fB;9lk&{S<4EpST^{#Tg$-*t8Kl4mTg~gjEfi2Tj2(wuV|VCqZ=D^e}5uViE12A1>xD9M^6Q z@Wxsz5T}pg(ePcbdR`KJ0jrIDC``KzID9o`{JjD~`{577C2NswBZ=ry?GrleHJxnWk9UxAY-oMgvmX=y2nr!bjla~ae-Tlb~mwWIl# zruGgz-;N(3s%1r%-Hp0E6l8zWqbBp40kuOM*VjeD^Yr`Z$jY6RUQcA8E$;KRzt_56 zcvy*5l`=<5`f?{TT8Xp#R&xwR$Pia?c5>i&C~J{CpD(+K`w37~9P4W$FZbEm!MgW+ zz*)woo`Ncxue136#pAPcfl{@ztbb@2mJE=Kw)3hkoP0&knfU4>Ged zGCXLkCZmaBC|6wCa`bA(5_M?i3eIGbk7gd%KX0}zzEL``6pM|8-6zzUqNh{`X>iC? zOUmmaovff}mgyKme0x4G=Zh>#sEU~Pyc(5&g+uD(hyP4V?k}&)Wz0{Sy)j>N_ikH< zFq^NybtH)O46(@_+W|>e(YYM1C@w9{#WT-5WxdiyHHn{LwZNf2vj>7Et{VJ3%Qf)h zc*cmz&!!A^_x8@%CX_N*_igR$nDwNTlE(Xz1AgRgZ~I_oSjrIxKrFBC9)&kcN+!3t zt0KOPTQswl8?aDGxNc#Up8>b!8af~)I6&b}!|3IP2Jaol+^cs@4Lsm@I}YYM-!bE0 z+7oFa^{Qj_>XXx7I5m`MvwM9bH9(>6VOzU9fTv}2ora)B0e`!~Ng#ZCO+#iJ<wLqNi9n@ z?L@?k{@RzpK0J74bJFnKUxrVGY@4rj(x zv1!L!RXH#8ph8mqR!_5Xq*6+WhVi~YsL7P^w^bJX`XNBpa-q6gYdTL}T%2OIRNI2= zMc}$*Z4(FOIl+t2Xz^}WgAYaE-r@OVkh;ocO}~lOWV4z!3Gc4+ckO3Oz;*}tY#*nl zV>U1GrX%@&W>w%|GwX}rwL{J~dXPL`%d#Z0SS)3jL|0T*b;1sOeSK+reIytd80cyh zYU+orcshNm3ge{lm)teyT6IP6cD+NfelpIr%gT?atgt1fF^uGew>cq_KtEATk$gVb-q@;0BnyQnXQjB^!r;fvpfzP$ssznq*E zwBzC7fp!-^P;vLioHmn87btYj=r&Uo2eyCVT4_NAxBlkRDANg2$(2m0YU^76*zWaA zg33dwsn6hZrx4x=Ijnj9~BjidmjKM&@oSU>Qwncrj*ewg0sN;F!SW9_;Q=irlZ! z^gh5HI@eu{iTH))!>axmh@uZ>`YEVmMS`4e$ms;JSnl|W(uLglj4$*ZlV?;!*dgL_ zEy{m34RD=O(4Z08ZZd9vhW!Fj&a^%vqa$c_Z+O>4E4r&o)OG3-4UO1Z_xB~9j4}jI zXj%p=UDY~h(tJy_Bvky?VK(4wVR6F`kH0_175?ES@URa_!j}&p1uul+(`V zVLr?EVUzs!QKK+tlT2JnBIP0s%M$zsBig*-qhnb49Bn?*VDla%QE0nSzF?K4oL2(1 zx;_#TJkvvbTw9E56NW zisKVKI|3;wsbsorKvlLc5QTn&joq^yYz5lH3NxEdMvE#BSe?kH1-(eiA_H zaI4)9>{CpAgIa3E9$;MT&#e2EiwXB=T7C`;uPApIWO7wv zuDx$sf2TO@rwX5XvV3%?3VpNBAT!&^`0|w&n?dUxS9oE0g7`=8`wgiRNy}r|El~2) z;*TE9BwN0g)#~Hr9qk;=?yg7RRKpW=@aD-RpyydPQ{n5h{V8!X>d}MN^GVOP>ymC~K$ttY_6b>&fjJfQzxo{>fW=&)4vu(cA099# z?f!LYT~8>ZY&X0)wcoZCX&7nLEy00H zr+*Jy*awPw{MgwDGuo`Wu52vb_i@L<5!)Tr=QBZz|b9TzgZ1njsb+ncpcxjp?MYR@?N)^j9{ta1AgEiC)+5o(|Eq#-(_e5MzV zI-wXw7havEYh(pnuT?~b$rjzFJw2nxc*Stnw!R;z#i!D01T&C~14i=kBk6nqL|Nx{ zeC1r`SoA*0jv>_rftuW;u-#k|K|K{)R&G!8tm7=~?-{O-@g9o35>m-Dej2^#CoFu_ zulacdK@smwHvBD<_Ru5(UH@K zkj~(l3o=Bay)G-TQ{j_vF_jl@++`V4t_!p0Vm7hNOK3@>cDE#DhpRmHT#jWE9Yet<=eDk&L=r`aW6&wL^g9Ku?`$d-H1gU3Jk z`mK@8`IeG~rGua1XL#U75^UtR>`uaplV%_288d9ki5)mcw+l)k=*w6VoJKjnxcKEC)^9=vGgYiFCkEokV}E3wv+O-QsV@6w^j-j zq8ku0^nE;Sudx(58Yu5^@(QSQb>Q@e%s$L>pTQYYGXL_Zo5q+O)Y%mgg8V2 z)2i7_qB&0!)g13vs=67k7(}(r4|$A{_C3|$^KG3_W#HDOO=Eg|>{(+A4*?eOB30Y= z+RF_=!_S|Up_?r!T{KV_{ZXB%P6Uk&iG^@3r{n9GrEpSsvigBhfj_gnb{tDz&2ty> z7W$WlM~L!XDTf6)_9%Nito7eB;qf4jA*wLvP-U!gKVT{YHlFWmm?zSfc%%tlF>NH4 zujsPA9l4w?M&QC%6b)#bT+i~0ca06N>d#n4mbQYjesxTum7eR z)uPRdT~xn-9k9R4Q`nnwEJ{v(qU1r9bKcqF>+WK4_=;=4pExIK7bn)xURtg5?C748 z66x(;)U({#KzFsS&u7=h#61LO7a4<4uL|mc22FBQVIc$uIoJ?MwKO%OMy0#j7&q5| zRDNj{fwzdh>Wqgze|6>2GIgG8LiCe}im73iRvQ-T6@geKRt*`u+Mt&Rl*^7Aa`I7q z0iRbk40eE5aijZJ&vD^v_?w=RG<#5R8C;d$DNTvFFCX>QfukYAF1Mn*A_I=tWTLno zHzN!A}kAY*-shE> zLnpFjXThs1M@wVayA=8$|XeoiJPUl0+Ov1+q=_EkiTX;of&+L@li? zBf)+2ZMp#!x?JR}q|w-=UkUj=aqqm}oH1?R%88oqMC+FqYMlzT;Z-{8dw40WWWA=S ztK{8ml}fht!6yTS2Wq^Jsp(pAeBuXur7yoEoTyWy-)= zVT0u18M#gwZ+m|^fp#7rBz3LlK`CyVRpz76HQ~;BhJnUjmB&-X#0vP zz?e_;$Hb>6b(Q4@S2w`aJ*#UxL*BT>tc`fPVC!VNjZI7YF-G{1C;O^1Qy8%-#7eYq zlED>a+DhxqEZ~}i1r@=j5e_W$*?&Pqx3G6fp$|%?4Dd~Hp)+&mOF-aoJ5i1tSrK`$58NW_p}*=8VzN zSD>MxfeUBioR?-KVm=q0p?B3rxgL>3}c$;|)8?@~b$_{F;I zJs8vO8_Q^pUQwv2P+9xCQnI}49^%KuW_plvJ4F0!r+}R}iBAbplyK^mU2EH@dLLRD zCu4`jsHfn0c68b^szelZUs(uZEkopLae*~Z8L=t^0r_q?Y8}HuFSaEMtUaA}3kvO9 zfM7M1f#%i1jEp5_KQ%Qx!Nqz!lE#ku7?&K6;>63cHqKJaw+G4DHF5edgeS_)u^(1Y zs!+2NfZGNRy!(^zPvqA|80wFUPTw?SnTq1T}GzazC|t#7*m1 zvX~iNRNHGQb=LbPCqXWvh_5YdMC0W$Le{OzK3sWHh}QNAqaAyrO%Si%uk$r$Kedm% zu1%?ImZMq8z;zGp(N^Onc-YnpDKr;(I(KLq$gpbjZ`8t14hMU2P& z&b{Ayd%+rSe}t1rj4nBEWUsy8oiV-)z~qN~#x75U;eGN`rS?66$@zA7dLr_3@M(^o3e13+44hIA7J z@_ft(g>S0%*>Y2Y@9>d?LPc7!-4cDNxT9cniPil#OL%i9W-4(yigD^_x}2Pxio)D* zi$B@5!s#GVf2d>(5^|~k{!hki)1vROgxC(`z0-eR7ifSPtPAyr_f)F+|@dew#Meo8}X-b z({uRnXaeszT6ITsH$r+*l zt1b#98p_-K0UBY!%=b?#?+v~QN0}(TPJuG%B1IYem|U!Dby}t7gl(FLh92pjl%YO6 z^Rvzq)j$UW49(%{ab|N!^gamXbzo?U*lF>0}ueI|(3_{dWWWphf?kw+nP58_$+y-<}cLxdhxRa%Xv zwh75Xw+d5nGQc%8><1wS;h8W2q&kf!>(`}umCa|=nK7qnQ#GS}8a;>otX!FIk~G~r z1(!Rp7p8uYjCvH1p-aw{Vlj=(3SkJSq_*D1y`=e?ub>1kq(DvmvD}e>)2cT+;gg|) zY|hPK(3{|st4HdC@Vj1rt#YHNA2K;fG~Rc|e7+}+XYm|*>`ZBhQr;m(dhr2}dr+QE zE-u~7KF$?rCKW9g$n};het2rky$W-qQt%QBNfxsNXUW7ZubY(Q*pxO+#P!ueqYTW} zv8q`h>O|G$NJ7qe*GE@1LiL&1ulwsFu7{IGI*t1f&8A@oF$9tChYoOv8BjG`0dVoidOsM8G4Z!|GOa*0KXk+hQf&6ACfg4lQfd z{z|?i!_adtSfC^kw9EAXMOwl1~r7wLV$W9K+J9XP7ji6GDkgv zz@{m#ZD`E#L2frA$Nf=Q>L-g33gh$pI1&YI$HVP?@jG9-t)YEclJAW*ks0@pi$Z>33HrZp(J`IpnME}TxJ`8H$M|r6#Zg5-h<13Z$_t|-C?#9SWCTf z(xU6W9*(A>)x?f6re7`{e$JXmTQb{;=v@_N(YI<+83_-clRYUY9ev&R15)&ybmdkD zcj7C5fKDJAY=10^693>p@ObHXFntV4VOn-l2tuFAb(m8rjvhjP?}OoiA3B54D{I&* z?B+uXra+J*?K8@UpL}IzLam>Wk)L7mxK=*2M#GU5lZRUeVmv_sKIq}&R7GDPE|LO< zgN)m9=Hu7NMEZL)5wB#|qnW3M6qIAYTvQ1^P2|+Z{l0yLqb%}Q7afSq<>Y`HH}ueM zzw$7}d$xcZBUXDdA*F)h9Gu?#0yPZNZTB+GRb4Pfs=&1=C7$q{%OpwL92T5{F|lf% zMsU7%t}lQQiIB1E`lA#*S#uGKC=k{S>=Rd?&r1O znW>vZf|JKL0j^g!b1cGA#c)()Ty+iBYrV!K_3wy~blK}2?!31+X|X#xI)ur$56zEg zM_MA~wij>GViQnp{QbmjN7Mfz;q6~;%H>=PjAy~(x`l=g zgf!J%mBp=WmYnX8N|yP0oMHs9^`9Z=vmkDw-*|N_J-O1`=HcTKXmv5*iDece) zSSbJgJVjLXU6ddC_AG#{-Y$0bcY6So8x zY(}U8-?_h|eM=Ng5=U9>_%RSxy(uot}5J%JDv`y z8$rpHW?ZKAkx{9dNueiGtFot6rH>EnBwW8|{zx^6f-+U7JchF8&g7OMXq;ol&P_8B zT5G1(GQ!If_B!9L#Jts(9A_30yMJ(?CkY9x|}Rky4AE|!n;1u7zuHf(g6={kb+4RT!{ zSQZm48&-g@lf#VoG2&9xD=&D_5+@kS9Z%2-s6!W_u#!syYK}$B$cVRJYX>Iq?a^;$ z(!A6}bx%UA$G(O=<&>htL!f)NWjZB8FCbiupkX@6=)m4xItwTlOu)2hpj00%#Am2qTUBYOfVlXH`|@ql6R`ejE!cNiIJOeBi4_YR}MY70Au`?cv+2y$5c*h&kO)0+JpKn+_ z7)7N#!^-&N=H^BZzd1!cD*mm05blRX{JB$R6WBmY;qawu+3e8-N_@iAtxiaw8poe3 znxc`=qs~TQ7rMG{Nl^RALA&4R)NWIJ^4IW>=22~#U94(~ES&MJ|@pBij-Nt8`F1O-8DCtkAHn{f~Xev=KYfu@9fAQebH6d0+Ju||Sm#fgYx~?+( z{rz7{WbBks=~Jlw2VVcHLdYZtL7nOh{>uya|NbkX!|jj6jbC_{>R;yLzce?!u9Jy+ z!CzP&cO;)Y0Q<&Z*5dj#)S^MQ>!W?@mMa80kHT~V>BLZLLG@(JM#aAZOVB)=Biscg zn#1Guq1kO>ta90ZL}6e;C2F$sfosi!`P@O(?dQK|tD}*0g0d$GQBSn?R#uju3$ymW zg!zAjVI4eFKP}qESihG24GI3w-F^fM!w&BLIr1O18<;yv3|n;1ixOM+UuMg{2Zb}g zApe*A^v@`dLB7W_*n3~inf-s{XUZ4(YyS?+Kga+7jvigxHQzs!$bYYk&W-X%=m#^f z82Ep9lfu18-Z203T(*(I7JL-jTJ%||Ul#e66#dw_e|N!`9Hq*)evfS(Hb&ETGj=Mn zu;g6cc5qR+K^mp`Whu->3~*_6#4*wZYr|ljt^OHfUvzG~_b?&x89WXB5;UHml6RCd zNCl*5h%i^bDoI-j8;$Gcfhw;C02wH4GX-)9Nx2YSP5tS>hPm%Q6Rq?IC=AD#Dx{{h zF%#J`b0s+LkM^Wu)ih$%X(B~&`yx{ z{ri2}Bd(WRBI9QFQu2!2%oFqIy7dTs@4ii4fJcR&;q6&s? zYjd};EHqJ&702ZC(x&NIY--ba1UZ&=;@QchDKujYv4%ZLym)*rZ?z=HmW(ThWO>|t zYAc~5?l3o>wsO$cH!PzUjFT*_WUfKGTt(mew2d zlc^i?Yv_>yJkEs;9S~NH7fgk?bXS>sYN>bfh@PIOXqPTDxEGO>qX*%RVm+;m1K7@L z7l8~4EweT@qurDjZMeio^dZrFHpu6L8mqU1o6K2xLcs0htOf)+bAl>!A5s&P7~gm= z$uFE<8W2`cVJ*BhE!bV6&LuVQ_xUqLOs^TLlaHannq#mJ)Oz3<`;H3HL&JKBBbL^T3o_kJ;vYg3bL3f8*4hEbgNdfqyZX z3DJhqffFL9tfCTvuUKbo*ghp^elas?|9O_{+o~@rV)U&D?b4TOdXls1HzW?E!XHV4q1jJb<^ujvc9EY@Dtcmgb2Hv?i zL=Bn3>AKY--bIE5lg}8aIFScgB4`{li{h7A)~VkV8D**nK(fYKVp|PMkPC$<+xFUd z+E|W@g{EOdSDw2juJjoR3Vn$cPvd!_ZGBuy6&bsKzmlGB za7^YwDN3$Chcm-htoy!$dU#5EExP7iFrNHZ>&avd(I;w@!aRlnZ)W{oV(#*#*)|#D zE#SipF8X$`w~F9=&)}mDd^oOei?^EXkF&(Iy^-FXZydkMq~tCR>pe@GT)1^<>gi~7 zyq_+<@$(dNeSoaAw|TDve7-ntVtBR`{ZxPm+pP)KIeJ&(2GA5Fo(<-5luc=+!yiyJ z#%}Hn?i9P)$RVevgPSJyPU*#Oq2la+Tf~l=h#|8v)W9kyB7!7IB6jr_i$()_b@5*e z6%fjUTZa-d^-*GaWAN<4;E;3>FyVzF-gZ>C_TE1j+qpVgQF%AA5iyS(DageJpry?G zP>_sNSNq9e_~s4`!fP0v*rHh`2JOGLC0 za3m$(F*^AEipHH#=ZkH3ng?q_Bt)z31y=9)IFnq5ih<_xOBeA-VWqo;9<+O?FEIur z%6F-jRvv$FO8`~p; z?(}aM4I*}ncFuM^#*p2yJ)Tk#78?@ZFoY43V5#iz57>LOp_VP(DOy^?J^0%4`ztZ5+Sjn#I``!4V0s zG8^=p?{4~KCP5&tIrLFyMwh3Y8=ef!>Sa(&d1G5F)F1ptqGAy;NyCeU8=|MffV$0dk6l zR#cSKP@FV<7KpuG*_We+5syAC$0OfNH0#gQgwj|3zoIoiLnz59^BZivm1yO_9%v7I zi)aDDvC$Yg^wOMQv@@fwvmSyGN=*67#?$11MQQrcH8VW;9dQHQ9r5tcccx!?&;T0f zb9G!c_^_J4$Oa9hJJ%JyePWq{aWHji`f*e(YUYMPNp7#K3MCQBchw?>_E~EtRJ(Q? zq{bjIP4+2ObZ-ot-bVY2FUA|SE($JW8n^zq)-)%?K8ZjNZ=l4?nnOlrKT0z4tgcre zHb#_0ALCr15e{g~w7_Hp+$n|kDpB_(6CA7NmK2fdOp5boCKqYSv)yehq|oT}WHvRS zKu>0z#qF5Qi^(M$q0NcHNnVn!AgulTz~p5ePqIU-{=s}fHoJ6?(`f((#f-ODCXi=x1@3D{N1%WnbL5ki8bOM9< z;QrqB4*d1eqj)a|Ij)o+?z?L|Is?4oUxz>vMPOb_A(`zeqe0{$ z#}qMf(qsEo2&I8xjW98G54?ropq)jf`o2uK9zi#Yw00{vnJ~-y_~vL-PKTKI zWRy>`l7tWJi>7n*8}fWoXC3$<6d}Lr$G(3cQp}O z#*vwT2fzuMjVIA!fI4oAu>Zz$lPN`}LAmPZ&CXqZXkw#Q^;MqmG*w5`)gauPx`|9>%THA zVH}E&r>0<|8nB(t$8{~=3L7@W;a&wQM_^&hIEml!GXIs=`E9kf#kj#Rk&R5RW`By=e^c6@@#wK|7YnEc8IG=dwO>rm zSewv-!+(Tg6QU82c}|(40ZqLDCx_88ZR{bYJHNgP$GkdDV$v7pk(D>bb9@6B*92zr zWGiJv|B;?H?Uqb?dDV*jo8W@;#y2PZmc{z*lKr z94|9VOQZQqY!JASrrQtJKlPEHPw3ngn{t7gP6|lRo?bWo`A^=3jz<67cN>;p;x2Z= z=ftk1>G(9tthwUw1@d3zV3fYQL+;h}j{Ml!87gZhP|Y-&<>@1=>}3>Hc~6HJ~tt z%eIxNsd!1q=;k$J$&vB}{wjs{OBC0|%93@ER{p9FSZYYZ9aueuZn;)PaQ8Ou6&e|N zteinT%&>V1?zbSVpdfa(Iyet5_nVA2g)gXkW<;idt<>}~e79^;4v&sh#z}ZIQ(MM^ zWLErt%MWXMi9xowQp-Ul|8M+}E-lP?TUPqZzd3D*>oUP?JH2mka8j#6vGnP4mV|3- z!?xG+ORma@D()R&RWUlf3%IgNhOs&Z{E+34ebGi zAu#3^N9PeRK2bm*AW?zyrlr{x&f9Yh($>yNay(CbOaK0In0afi{gngXRZT}1St<%Y zY-EH1k(dn@Y#1tTtw`L}-F*&Lc*SL8Kz{%J{W#~x@zr>H&$!R6+O^3_v-4>WGl@HF zX1Zo?e}9@WQ6YX7cw1lj;rpx~8d7ZvUU0@rc1mixCtm;iQsFTBEiUR1D$3;dmJkyY zpW50xi;9Z}z9)kyiW8-!JG3a#G&AFQeJ+$@wcoZyB;myr0zDBgJ8nhca{olp_qk&1 z>FHGwH#WT3?9~FDyO-)UBcow{-ZZ}-Yd+hY{bJKwnB@teioCf&y1#4B>TPLssC1u6 zDi{T~DcG7G7Yqn#4oT5^-H3_#+7)Pr%V*7h+IlBAlEQ}r2Pbeb_vtX) zX5OO$e@G4nBq#sX+S+EqLV<;6(MvYnhchLW>x&kV60*9G>}>28C2P;jCVD&=c9@?v z>}^dYOhbjrGlgI#Stl`--g>}nsNj;45l)j$z z_6&oLfgV{To4b^qN1Uh`yWI7?8E7<`MqcI$w0pjnv)v(5nf1fVLqP6-D&Pj>4&)5)SIKZn7FJj%GxS$+R>9; zQXVpgNq=aC3yZuLMPR)3kE8{BQLJ*{t;)Zv4L38?^0F>%3;xA(1T(&7The`#=YxIu zO?XFpmIrd~{;H;mwgkNf50yf?Z=yyx1w1914q3}~eqH`dD4nJz4p_2|e>7K?EBEzd zORHC8koj@4+<9}zTWD3T)YJz%6u{Qz$h`RPu$$rTZj3CiHV7r5D?~=qCsxb-k817W zQVA4RgYI7RgZMmuP#Q-h2;^r->$0LEVK;RSPu#`Y(QPl}$F+{H1eXC=_~?k=R~4qf zv;EI1>4yOqYE1LTzsPw7V6NSb5s6PYL2FjMJA-HHRi>=7vPNgVd(a3($u zR*lq;yXS|ps-0!kBodBHrsg}>MFw{1~vW~xpWPRn^; z|1Po`DvRyVjXt_t%I>*{jFF`!1hDCiU&@0t1fSTb+i*&LY}Z)ZXUzwD>DiYD&9ZAh z8qZkytRCh%jkT3U9Hr&tszY!>=*f5%KghJ#{j*$yN34?kV@@ABr*p`Xmqpgu=QARu z`L)_s&eF@v!+jd(s;zh@{pif&mkY}4KXzOhQNGsoGYjRRw^IMUEB|4X`&rBU@r!CH z<)!&&ZT{yfyaZrqIO8PT)aTzB`Wp`3PXzB+PQ$~$0{$n4`D+$`)}Von&KN5~#hI(u z-c4d>@83t^v>RhYy8Z?buhnNdsI20*M}C>UfC`i`G|Y7)^pO;*lUo10MtOAhtJ!1a}JNs=W;aEadSvj<{RG$3f*5>A@WW1vDQ_CPRWlD;+Nw+Ksi2>MT@^wz1lFat% z@^Z9|@pZXjr>98eZN`?GJ9b#WT<~#U4b12PoGv$-RN&)D$;cF=8MU%j5F+{Ul#va^ZD z+t0}aU-!kVBSFFOmLykhqlfCQ7ri*V3~k$EK01pkd4+|B^Of1@J&*XE7xx%SWK_J+ zUQf3tj+;jhIQn2_)R&7hpO5?=O_R0l3fms~+4JXZL+6 z-`(}bB?TK3NqjExh+n(2Y{yqz>a5VgRW{z&hl=lU@$msAoN0bVV)KVMbhDN9mA9inTNp!TWb4s->?x%szpz zZbvryw@nMxPW0Yy=iSKy7Mx(4i8bePv8Z|D#46A8PBzal2k?LH_P}hm@x}%2(EB1=p}-Cw96k6d`WhyeI#z=vS~P<9UyFI}Uii*F z73O8JIUnTKey^(Rqui>eI^V=x=&D*A;;vqvdnz1YE$)6z{j!rssz!3;QeC1HJ?tVf zwAeu2PZ@?~da_tY!&KIVsl!eo^XdfN$6xYE?16gb%f`lx<$eY#98i@AJBv>Uur z>RA?Rf2MpZcTa7~CoUMS@IU{DseMi7y?)b&$M zMdnibQW~gv#5bY8WUts~MOk8Vx$tXzlg(tr^qPPLHp{09U(>*W107?t46wGZo-AX& zSMrrFd;Fi3I-%pkU-d-!&-GwBpcJtOdX$2J(q&p2KCAaJ4dQl&t=%x6w)uKf8d&f9 zvn2<=I|QXa7>U3%?g7!o>+PvvfbXm=qRi1OJVNO!-db)>WNLma0t*5BH$>^Jb< zi@c|0$6xihTj6uqO}iZ9+8aqRT4-k0L*K*zH{&7!_k`Owte%~j8Jx z7lXkQSAx}Zpp5EgEgG=p2{oSGnZU2(`199;@rn}lBDKQkYR?B*yLWyq4+YugXt~v+ zFU+^%p%1EdGeWUcFwbRr&5Ga-R8%LE*lAogz^)ts>27Uusa6|AxgFM2#TkcvKaq>H znRc<~C0v<8qpG?((g*;vW;Qk-DlvGx{a&`-Mo9zlKv8Pv4eSwtE=9A5uz}3!vZ_l< zN9W(0AF%AQ2JS(DEV;mLL|o3^4t95;J&2Bs{0v)^a3&N>+w4*(8)HpG$;m5Qq3NGz zvsOsm8iNM|8(>DNa ztwodIaXfF=Vf|s78O?>ur4h2{rl~XFX3BDs3NdE&FmVpv5WaHZxS@(Y^Zc>J>VbG3_#Zt=KrDU8@MCkwqRpB6I&B| zVsm15Y)&S&Z6_T~FmWcy#I|kQw(ZyVzI*Tc-Vf-tx_g~{_C8g$tJw6&$1}JVp<&9v z{l^x&UN|(qDt@5&6iLAtaB03Ou&!{?(n_ZXjDZ3dj%w>LV43i37R)KkG;@q?{_J?2Dhe!MKd6@OqNJzCv{}2s9j;AHy_7BZKui*guZG_p_zT!MRW`}>2MkYBAZu3Jeg!4`p<|S9@|#iE zsV~=a)c<{pCE`-PAaWr2Qi<#F5rUHpC(_<)^F0{55tPvJ{8GvsN9*I1a|bP!%S=Pl z)viMtlt1PV)K!XS))3>#;Pr{^8*EH5>a`2ZYu2;vd0-8`wF zsBq-az?Wj5l%Nc@oM%+Mth=3<)JM^zGpRIE#%jK1f7Fcsq)45@A!(0@cBaN9vo0Ji z^?H|DO5HYrn`cgBz&w5h-PTv+1c8UR?{q)aK$(koDbqhW$~{PpQ~voGwtCYt014L} zORBqmFq{r+QJqmIP{}$$=B(2yic=5O@w$=g1KLVCcg=N`kyNS3R`DbqPgwei^Ui!YWh5J-T zLx9r>sWE;>T@+Q)gG2zi5!? z?@#goKp5j(1?BAgD|mr-f@eWbx83w*toax6i34m|FB35^LLy1JD{IdRl#%$dmXNQ& z%UmzsiOXV`!~Z_!RAaJe%d?_XJ_&A(=P)V2b~ivE()MR$R#7MjnmB{Q6GO_}TCVKw zVja;*7;Q)^2t#_rxkfEa@M$2kT?OLde&T9jC-5T{X7{s8wcyW1I1+KID*j*my~joI zk>u&6%}7oQB0>kq%`{Uq7P$Y^a~E?^m&0a@!JoHTCJs(cN!!-<29ps?jZ#?DGwM#? zvs{yr^I^7p#H9usKB`=KKE3)_TXcle8Dc871$Gw~-C>f_zN6;DtkiZquiVUvh02m& z$ONwJWfZGbe#d;>qD>6iZAhlML!NJ5l}RzSF1H8cVIk)2cc*SK+7_;t8;t8MuKgX! z8M$rc^PM1z0g{3tS*&~59RHb`oy)@@zE7ZB_mpjY;B^|~o5DG9UoESNYVR;Z$_QY@241$urI@jsr`+DGXWf{T5; z4Q|Tz=d$srsY4)Nz=iLlAFoxHYs*LmWrFmPr7H4PgBtdja@L77n1gXa9GCvOE&x0F z)ysC`fE&=OA?8U8y`fR2rHR`_C@N!*3&z6zdH*xv`*zF0nX}sC9jLO)-wr$NO8Isl z*{ol{T?(;zb~akgG-M)oK(=j+mmROf;gf=RY^KH9a7(W3o)2m`Qgmb4z7HjJ;=*xl zZ@PdX>6aBs%Aqp6*&Jazb#lcX>CyGK=UX*&KadYy)ubiFkcHb_z&R;InhGj&9Wkkr z$zaTPz8HoGutz{Y_|MRI_u1M6XG*ra1fU&aOdOlJ5 z^RJpQf{EeS<3`AB(h-AUBtF50touJD@~9p=KcC~YKYzS?-X(H#nV<$^-K;D*z$Aq5#F5b)fXM5m5ppjc@EI8Ffp zz9n;_`vnYW8a>rYSM!So(!4J%rKK5jhPo-;z-IINpPF>C`ygFvxi*{~*zvZ2nP$>c z^D$ninyaEqioOJOTX^+eY!((h7(Yp zS}8}O`c;W)k&LM+BG#`cU{auVZ4&#pEN(JS%vpL%r$3ulbU$giV@`<2P`n*{hif!R%tA-Y z=XNfN7Ugqt!C}+`#gN4(#zUI~C|&$(7W_yOfQSZZKGn_wuKtzzteTFFL0wc#bZ6(R zASIvEW(ROw_LrfTj)U=wdLFMkzj7xTJSjS2&55$r-{kVIP6xlRG=a>a}-C*KG1IPWM;sZpH|Y(6BHOOD9N=(S>50BvKX@VR*&c zgYBo-iiELUcfon3A2^KqbfYPZ^K7^=@hKD-Sajw|gR`?@Z$?yC(CE&PTD*~;M_&PY zdf;5ua7nAW6!*=otu;d=l#;E8C@2X6UMQsaro>WC{u@q5N`+rilVqILpvsIcWL!Vq z4uyJr&Upa~I1IYcV1(IS$k$(8F!LFTWGI0f`uab$_&o2bVEl9Vii=VkN5%$^)gSLQ zHvOjnrBS~^co_Y$!Xo=88-uhx_zlS@;f;*-q&c5?y(akYgw95TWc6e<&qLRpA;W)` zl!<>n?~;6mw_63}L<>Qw;FO+^A7*spPmiIiiy`=T%ESZvWmCX9@t2!Ta2U{3`!O$U z!sx7~iri?)_M8|tHzyTWLlPLZgqYu^=ci?o{8sMpWUR@jKKN8;tg+?lNc6B(w@3$hV;jdOF=^=&X|x0S_y&tObHu2Yqe4{B>A zm(>pOkTx=FPf}l$K7=EoGA~?;N=tH)GB3oIZEB1n>^VGt2Hae3JM8TGPRqR)#e_6Z zlm6ejL4v@6gut0+X6fw}iWXqzbNfTCvWB+Kd~HIyxUgX4@}@(Dvo@##ms&LOn{VhX zL^ijrqQ4_mhXb)CA7Ju{@`!N0+)APzD#lqDz9}l5mqC_%@AHZ>MAoGKOj)PG7Ru}a zsk=&-!$OQ|Fj*Q9p!7FKE;$*;>Ki-Vd_xf!t6~7d)zYA0C z;~X>jIX>P{+*cN8Ek3#{NU2Tz^Y70pIue9u`g6O%S4_-QZ*eTb_j65}p0IsC18@Du z83u#un&H|Q5}TGf-z|Oo3j^99iLeV(*6HcLKeCRxyK+d6H#k(1{2!r)Ir6W{O9;VP!vjn{S=R&$|m?S_8X1Rk9nKYa1?E}0D>D*c!!~zQX@4KTv zW)16qDs59sM}izJ18^W9AgZY+6j9EEa{9s-G_(US49%P%jm9S?B=-h5i%&VF6%{do z#j~;~z=W)TaR1L>db-Ek^&*=pr|&fTp2aBbIA2CdMLFah38`wc_K0eux_5;UE& zGy=glb0Q(aQ<)Bb5v&6=koxYDU7FbaV0)q)sG=JM#td+jKnlEV;u(%_u*X8VjrVAc z+FLHApgL75e>1z#r|tD+73-LQGIFW}y|Vsu?-N7dU_IwK_R8O8M|&GQcDyMWy^^sw zziZXm2O!~pQ+iymzeLnatEq8HHa!oh8QFuOA1keFj|I4qK*38bZb1ro-OmBsU~o0t zJ=^)O`w~?&bz!UqjQ*AB4uf!YdmCDFNYVM(OR^u+`7Uvr$0i*GJG%GrXPDsq+qw^GN5DEfmr^x} z>ue00)d2(YP`8gstBOjYRinbc^1EXyEGTq2C6aoUbg-V%B0L-_af0}#$ZITndC%;s z`BLFGZ;9zyQGNgP`0P$)?6lQ#mgkG0TJq%E@#u

OE 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 z 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 literal 0 HcmV?d00001 diff --git a/microsite/static/img/ccf_vm_blog_img_4.png b/microsite/static/img/ccf_vm_blog_img_4.png new file mode 100644 index 0000000000000000000000000000000000000000..16582ace461215b1ed0a22ccae5b99066e92c3cd GIT binary patch literal 29439 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 Pd7%d+4VJ2sFbw{G6e59U literal 0 HcmV?d00001 From 4ea575fecd0927552b3e0491ad483892c6737e44 Mon Sep 17 00:00:00 2001 From: Cam Casher Date: Fri, 1 Sep 2023 10:32:23 -0600 Subject: [PATCH 233/251] microsite: fixes page break issues and removes welcome post --- microsite/blog/2019-05-30-welcome.md | 13 ------------ microsite/blog/2023-08-23-ccf-on-vm.md | 29 +++++++++++++++++--------- 2 files changed, 19 insertions(+), 23 deletions(-) delete mode 100644 microsite/blog/2019-05-30-welcome.md diff --git a/microsite/blog/2019-05-30-welcome.md b/microsite/blog/2019-05-30-welcome.md deleted file mode 100644 index 84c8771b4..000000000 --- a/microsite/blog/2019-05-30-welcome.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -slug: welcome -title: Welcome -author: Yangshun Tay -author_title: Front End Engineer @ Facebook -author_url: https://github.com/yangshun -author_image_url: https://avatars0.githubusercontent.com/u/1315101?s=400&v=4 -tags: [facebook, hello, docusaurus] ---- - -Blog features are powered by the blog plugin. Simply add files to the `blog` directory. It supports tags as well! - -Delete the whole directory if you don't want the blog features. As simple as that! \ No newline at end of file diff --git a/microsite/blog/2023-08-23-ccf-on-vm.md b/microsite/blog/2023-08-23-ccf-on-vm.md index 3c685b5bf..9201698c5 100644 --- a/microsite/blog/2023-08-23-ccf-on-vm.md +++ b/microsite/blog/2023-08-23-ccf-on-vm.md @@ -12,9 +12,11 @@ One of the most popular options for both piloting a CCF instance as a proof of c Virtual machines come in many shapes and sizes across different cloud provider platforms, including AWS EC2, Google Cloud Compute Engine, and Azure Virtual Machine services. For the sake of this article, we’ll be focusing on how to create a CCF application running on an AWS EC2 instance. However, depending on the chosen operating system or distribution of your machine, the steps should be relatively the same! ## Creating your EC2 Instance + CCF at its core is a Node application running Express.JS for its API and React for its client. So once you have the environment setup for one endpoint of the application, you’ll be able to run instances of CCF’s API, CLI, and Client on the same machine, and switch between them if desired. Before we do that, let’s create our machine and set up our environment. To start, we assume that you already have the following: + - An existing AWS account with permissions for creating and managing EC2 instances - Followed the steps for setting up billing data for your [cloud provider](https://www.cloudcarbonfootprint.org/docs/aws) (i.e. AWS steps 1-3) - Basic familiarity with navigating the AWS console @@ -24,6 +26,7 @@ Let’s navigate to the EC2 dashboard and select the option to launch a new inst ![Launch Instances Button](../static/img/ccf_vm_blog_img_1.png) From this point, we’ll be selecting the configuration options for our new machine. While the free tier may be tempting, we’ll go with a t2.medium. I’ve found that on smaller instances such as a t2.micro, the limited hardware can sometimes cause issues when installing node modules or running the app. So we could use the extra “oomph”. However, for the sake of your own instance, please consider the following: + - If you’re expecting a large amount of estimates, consider a larger instance with higher memory and compute power. - If you plan on running a [MongoDB instance](https://www.cloudcarbonfootprint.org/docs/data-persistence-and-caching#mongodb-storage) on the same machine and persisting a large amount of estimates, consider increasing the storage capacity of your instance. - If costs and efficiency is top of mind, consider choosing the option for an ARM instance as you will not be able to migrate from a non-ARM instance afterwards. @@ -31,22 +34,24 @@ From this point, we’ll be selecting the configuration options for our new mach ![Launch Instances Config](../static/img/ccf_vm_blog_img_2.png) - You may also notice that we’ll be going with an Ubuntu image as our operating system – a popular and widely accepted distribution that you can use with any VM host. You’re welcome to choose a different Linux-based operating system such as Amazon Linux in the case of an EC2 instance. Amazon Linux serves as a Linux distribution optimized for running in AWS. It is also optimized for running most Linux-based software making the steps you’ll follow almost identical. For the sake of keeping this tutorial a little more friendly for other cloud VM services, we’ll be sticking with Ubuntu. After making some final decisions in creating a key pair (required for connecting via an SSH client) and choosing a security group, we’re going to hit the even more shiny “Launch Instance” button. After a short wait, you should see a notification that the instance has been created and is running. So let’s connect to it! -*Side Note*: If you’re more comfortable with the [cloud provider CLI](https://aws.amazon.com/cli/) or other ways of configuring resources, these steps can be replicated using those methods as well. +_Side Note_: If you’re more comfortable with the [cloud provider CLI](https://aws.amazon.com/cli/) or other ways of configuring resources, these steps can be replicated using those methods as well. ## Setting Up Your CCF Instance + Connect to your instance using your preferred method – whether it be in the cloud provider console or through a local terminal via SSH. Once you’re in, we will need to configure NPM and Node so that our server can support CCF’s code. ### Installing Node.JS + We’ll be following the officially recommended steps for [setting up Node.JS on an EC2 Instance](https://docs.aws.amazon.com/sdk-for-javascript/v2/developer-guide/setting-up-node-on-ec2-instance.html). Henceforth, we will be relying on NVM to make this an easy process! [NVM](https://www.bing.com/search?pglt=641&q=nvm&cvid=ef35fe5448b345eba7740e8aae9b0b8e&aqs=edge..69i57j0l5j69i61l3.383j0j1&FORM=ANNTA1&PC=U531) will manage our versions of Node for us, making migrating or downgrading easier. ```console curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.5/install.sh | bash ``` + We then need to activate NVM by running the following command: ```console @@ -61,7 +66,7 @@ CCF requires NodeJS 16 or later. While I prefer 18 since it is the current LTS r nvm install 18 ``` -*Note*: Make sure to check the latest version compatibility that your image supports. For example, Amazon Linux 2 does not support Node 18 at the time of writing this article. +_Note_: Make sure to check the latest version compatibility that your image supports. For example, Amazon Linux 2 does not support Node 18 at the time of writing this article. NVM will also inform you that it is setting the current version as the default. This is useful for whenever you need to switch node versions or have node disabled for some reason, as you can use `nvm use –default` to re-enable it. You can verify that Node is successfully installed by running `node -v`, in which you should see an output of the version that you installed. @@ -75,7 +80,7 @@ To install Yarn, we simply need to enable corepack using the following command: corepack enable ``` -With a little magic, you can now use `yarn -v` to verify that yarn is now installed and enabled in your server. You’ll see that version 1 (yarn classic) is enabled. If you’ve been reading the CCF documentation, you may notice that it requires Yarn 3. Do not fear, CCF will take care of the upgrade during its installation. +With a little magic, you can now use `yarn -v` to verify that yarn is now installed and enabled in your server. You’ll see that version 1 (yarn classic) is enabled. If you’ve been reading the CCF documentation, you may notice that it requires Yarn 3. Do not fear, CCF will take care of the upgrade during its installation. Now for the fun part! @@ -105,9 +110,10 @@ Use `cd my-ccf-app` to switch to the directory of your app. You should see the f lerna.json package.json packages tsconfig.json ``` -*Note*: If you run `yarn -v` again while in the directory, you’ll see that the version has automatically updated to 3.1.1 like magic. ✨ +_Note_: If you run `yarn -v` again while in the directory, you’ll see that the version has automatically updated to 3.1.1 like magic. ✨ At this moment, you can either connect your data by manually creating a `.env` file in either your `packages/api` or `packages/cli` directory based on the `.env.template` files in those same directories. Make sure to check out the documentation on how to connect data for your chosen cloud provider: + - [AWS](https://www.cloudcarbonfootprint.org/docs/aws) - [Google Cloud](https://www.cloudcarbonfootprint.org/docs/gcp) - [Azure](https://www.cloudcarbonfootprint.org/docs/azure) @@ -120,13 +126,14 @@ If you’d rather skip connecting your data altogether, you can also [run with m After following the setup method of your choice, let’s top things off by doing a `yarn install`. Once the dependencies are done installing, we’re good to [start our app](https://www.cloudcarbonfootprint.org/docs/getting-started#starting-the-app)! -You can now use `yarn start` to concurrently start both the Client (react dashboard) and the API (express app). -- If running the client or with mock data, your CCF Dashboard will be available at port 3000 of your instance. You can view the dashboard by navigating to the public IP of your instance followed by the corresponding port. +You can now use `yarn start` to concurrently start both the Client (react dashboard) and the API (express app). + +- If running the client or with mock data, your CCF Dashboard will be available at port 3000 of your instance. You can view the dashboard by navigating to the public IP of your instance followed by the corresponding port. - Please note, you will need to configure your security or network settings to make this port available - You can also use `yarn start-api` instead to only [run the API](https://www.cloudcarbonfootprint.org/docs/running-the-api). You can verify that the API is running by making a request to one of the endpoints on port 4000 of the instance. - For example, try making the following command in another terminal instance: -
- `curl http://[your-ip]:4000/api/regions/emissions-factors` +
+ `curl http://[your-ip]:4000/api/regions/emissions-factors` - Please note, you will need to configure your security or network settings to make this port available if attempting to make requests outside of the instance. - You can also use yarn `start-cli` for [running the CLI](https://www.cloudcarbonfootprint.org/docs/running-the-cli) and requesting estimates directly within the terminal. @@ -145,11 +152,13 @@ You can always reattach to the session by entering `screen -r` in your terminal. If you’re more comfortable and don’t like the idea of having terminal sessions running in the background, you can also create a `.service` file to [run your application as a background service](https://stackoverflow.com/questions/4018154/how-do-i-run-a-node-js-app-as-a-background-service) instead. ## What Now? + Now that you have an always-running CCF instance on a cloud-based virtual machine, you can now continue to explore both realtime and historical estimates for all of your services in the cloud. If you’d like to explore additional ways to enhance your CCF app, consider the following: + - [Running the CCF App with Docker](https://www.cloudcarbonfootprint.org/docs/run-with-docker) - Creating a cron-triggered cloud function to automatically fetch new estimates - Configuring a MongoDB instance to [persist new and historical estimate data](https://www.cloudcarbonfootprint.org/docs/data-persistence-and-caching#mongodb-storage) - Using the CLI app to [seed data into the configured cache option](https://www.cloudcarbonfootprint.org/docs/data-persistence-and-caching#seeding-cache-file) for your instance - Creating an internal dashboard for your team or organization to view estimate data -Hopefully you’ve found this walkthrough helpful and see that this is only the beginning of your cloud carbon footprint journey and taking steps to help create a greener cloud! For more walkthroughs and technical deep dives, make sure to keep following the [CCF Blog](http://cloudcarbonfootprint.org/blog) and share your experience on our [discussions page](https://github.com/cloud-carbon-footprint/cloud-carbon-footprint/discussions)! \ No newline at end of file +Hopefully you’ve found this walkthrough helpful and see that this is only the beginning of your cloud carbon footprint journey and taking steps to help create a greener cloud! For more walkthroughs and technical deep dives, make sure to keep following the [CCF Blog](http://cloudcarbonfootprint.org/blog) and share your experience on our [discussions page](https://github.com/cloud-carbon-footprint/cloud-carbon-footprint/discussions)! From 37479a3f66bb50f0170d1684e2f348f199188b9f Mon Sep 17 00:00:00 2001 From: Cam Casher Date: Thu, 7 Sep 2023 08:53:05 -0600 Subject: [PATCH 234/251] microsite: adds ccf on prem blog --- .../2023-09-07-on-prem-data-collection.md | 162 ++++++++++++++++++ microsite/blog/authors.yml | 3 + .../static/img/ccf_on_prem_blog_img_1.png | Bin 0 -> 91508 bytes .../static/img/ccf_on_prem_blog_img_2.png | Bin 0 -> 167878 bytes .../static/img/ccf_on_prem_blog_img_5.png | Bin 0 -> 324956 bytes .../static/img/ccf_on_prem_blog_img_6.png | Bin 0 -> 372239 bytes 6 files changed, 165 insertions(+) create mode 100644 microsite/blog/2023-09-07-on-prem-data-collection.md create mode 100644 microsite/static/img/ccf_on_prem_blog_img_1.png create mode 100644 microsite/static/img/ccf_on_prem_blog_img_2.png create mode 100644 microsite/static/img/ccf_on_prem_blog_img_5.png create mode 100644 microsite/static/img/ccf_on_prem_blog_img_6.png diff --git a/microsite/blog/2023-09-07-on-prem-data-collection.md b/microsite/blog/2023-09-07-on-prem-data-collection.md new file mode 100644 index 000000000..75475c0f9 --- /dev/null +++ b/microsite/blog/2023-09-07-on-prem-data-collection.md @@ -0,0 +1,162 @@ +--- +slug: on-prem-data-collection +title: CCF Data Collection: On-Premise +authors: ksambrook +tags: [electronicarts, data, on-premise] +--- + +### Using enterprise tooling to collect and populate the CCF Data Model + +## Forward + +Embarking on the journey of collecting data for your on-premise resources can at first seem like an impossible task. What data do I actually need? Do you have the right tooling? What stakeholders are you going to need, and how much time is this going to take? Where do you store the data? Most importantly, how do you get the data once you identify the tooling available to you? We’ll cover all of that below, but first just know that despite your initial trepidation; endpoint data exists in countless tools. With a little creativity and knowhow, you should have no trouble identifying, retrieving, and storing endpoint data to be utilized within the CCF Dashboard.. + +## Getting Started With On-Premise Data Collection + +### The On-premise data model + +The CCF CLI takes in on-premise data using a predefined model. This model dictates the type, content, and format of the data needed in order to facilitate and proper and accurate data import and manipulation. You can view the current custom data mode, as well as methodology write ups in the [On-Premise](https://www.cloudcarbonfootprint.org/docs/on-premise/) parts of the CCF docs . The data model contains a lot of common fields such as CPU cores, memory, and where in the world a particular machine is located. We’ll cover these in more detail as we continue on, however just keep in mind that we need all of this data for each endpoint to provide accurate estimates. + +### Data Sources + +Your organization will most likely contain a multitude of sources from which endpoint data can originate. From computer accounts in Active Directory to System information stored within tools such as your Antivirus or system patching tools; large swathes of data are stored. It’s in these places you’ll be looking for your target endpoints and pulling relevant data from these sources to populate the On-Premise data model. + +Endpoint data can also come from some unlikely places. If you are finding yourself lost in your search for endpoint data, think of the following list of tooling to see if perhaps your IT department has one of these tools: + +- Antivirus suites +- Vulnerability management suites +- CMDB applications (Inventory management) +- Configuration management suites + - Puppet + - Chef + - SCCM + - MEM +- System monitoring suites + - Nagios + - Cacti + - Grafana + - Node Exporter + - Telegraf + - Prometheus Databases + +The list goes on and on. There are countless tools currently available for organizations to perform a wide range of tasks related to endpoint management, monitoring, and protection. The wonderful thing about tooling such as this is that they also rely on large amounts of data related to the endpoint. Antivirus tools need to know if the machine has been online and communicating with the management console for updates and status information. Monitoring tools rely solely on doing exactly what their name suggests–monitoring endpoints. CMDB tools are directly responsible for endpoint and inventory management. Many IT departments consider CMDB tooling a source of endpoint and inventory truth. Data here is likely to be very accurate. + + + *NOTE*: Keep in mind the permissions needed to access these data sources, and ensure you’re following your company's best practices on the retrieval and storage of this data. Where possible, always partner with the data source owner to ensure you’re not only getting the best data, but handling it in a manner that doesn’t expose your company to unnecessary risks or security incidents. + + +
+ +Data sources are the most important part of embarking on a journey of tracking and estimating the environmental impacts of your enterprise. As you begin, be creative and leverage your existing partnerships with various departments within your organization to find these unlikely sources of data. Continue to rely on the On-premise Data Model to match data sources with the required fields. + +When choosing a data source, be mindful of the size of the dataset, and your ability to continually pull data out. Intermittent, or services with unreliable uptimes are not preferable sources. Data sources with large datasets that cannot be filtered or compressed may prove difficult or costly to ingest. Always try to choose data sources that have exposed API’s if possible. This will make automating and scheduling data collection far simpler in the long run. If API’s are simply not possible, strive instead for tooling that can generate scheduled reports containing the data you need. If possible opt for CSV, JSON, XML outputs that you can use easily with your data collection and storage processes. We’ll cover this in the section *Collecting on-premise data*. + +Before we move on to the means and methods in which you might collect and store this data, bear in mind that some of the fields in the On-premise Data Model are to be collected over time. We will go into greater detail about these fields later on. Just understand that your chosen data source does not necessarily have to include this data. You may be able to generate the data with good automation, and scheduled data retrieval from other information provided by the data source. + +- dailyUptime +- weeklyUptime +- monthlyUptime +- annualUptime + +Once you have a source of data to utilize, you’ll be using that data to calculate the above fields. + +### Data Collection and Storage Technologies + +For example’s sake, let’s say you have chosen your AntiVirus as your initial data source. This tool contains all the basic information needed for the On-premise Data Model, and it has a robust REST API that can be utilized to fetch the data. Where do we go from here? Let’s have a look at a basic workflow diagram. + +![Workflow Diagram](../static/img/ccf_on_prem_blog_img_1.png) + +This is the simplest possible way to represent the actions to be taken when it comes to collecting and storing data. Although simple to visualize, the means in which you achieve this goal can grow from very simple to very complex depending on the amount of data you will have, the length of time you’ll be holding onto it, and any security or risk considerations being taken into account. As with data sources, there are countless tools and techniques one can use to perform these actions. + +Because we’ll need to capture this data over time, choose a data collection technology that allows for accurate and timely scheduled runs. Being able to collect data, daily, or by hour will greatly improve the accuracy of your data. + +#### AWS Glue with AWS S3 Data Lake + +This is an excellent cloud native approach to capturing large to extremely large amounts of data easily. AWS Glue provides capabilities such as scheduled, and interval based run initialization and the S3 Datalake can handle extremely large amounts of data. + +Additionally you can pair this with AWS functionality such as AWS Athena, RDS, and Lambda to make transforming, and retrieving the data simple, effective, and accurate. + +#### Django with Django-Rest-Framework, Celery-Beat, and PostgreSQL + +If you have a Python developer available and are interested in a cost effective, or free solution; open source may be your best choice. Django is an extremely robust web framework built on the “View-Model” strategy. It contains an impressive number of built in capabilities including “CRUD” functionality which makes creating, and using database models a breeze. + +With the addition of 2 additional plugins it can provide all the data storage and retrieval functionality you would need. Django-Rest-Framework makes creating API’s within Django simple, thus aiding in retrieving your stored data. Celery-Beat is a database-backed task scheduler that can make scheduling accurate and timely data collection a breeze. + +Django also works incredibly well within containerized environments such as Docker, and Kubernetes. + +### On-premise Data - Data Model + +Much of the data you’ll be collecting is very straightforward. Machine name, CPU type and memory count may be readily available. Some of the data however requires special consideration. Let’s explore those briefly. + +#### Special Data Model Fields + +The below data points will require special care when collecting the data. Take care to ensure that you build these considerations into your data model to ensure an accurate data collection process. + +- machineType: + - Different machine types will generate different power loads. The formula used in calculating power draw for different types of machines will vary based on the type. If your data source includes a type, you should ensure that it is formatted to be either “server”, “laptop”, or “desktop”. Being unable to provide this information will impact the accuracy of your power usage calculations. +- cpuUtilization: + - Your data collection tool may be unable to provide this information. If that is the case, your best option is to make a best case estimation. +- [daily,weekly,monthly,annual]Uptime: + - These 4 fields are the most important, and also require a good data collection process. Using your data sources, you’ll need to provide incremental totals for these fields using uptime data if available. Not being able to provide this data will lead to very inaccurate carbon impact estimations. + +## Collecting On-premise Data + +Once you’ve identified a suitable data source and method, you can now begin the process of collecting and storing your on-premise data. For the purposes of the write-up we’re going to use an “Antivirus” as our data source. + +### The Data Source - Antivirus + +Our data source is an Antivirus suite that provides up to the minute endpoint stats that cover all of the basic sources. +- cpuDescription +- memory +- machineType (Server, Laptop, Desktop) +- machineName + +In addition to the basics, the data source also has some additional data which will prove useful to use later on. +- lastAgentCommunication + - This tells us when the endpoint was last online. It will be useful in determining system uptime. +- agentPublicIP + - Knowing the country and region where an endpoint resides, helps us calculate carbon intensity. The public IP can be used to identify the geolocation data associated with the agent to put this into practice. + +#### Collecting the data + +To make things easier for us, our data source also provides a REST API in which we can collect the data in real time. If your data source doesn’t provide an API, attempt to get regular reports in CSV, XML, or JSON format that you can use in lieu of making API requests. + +Depending on the method of data collection and storage you’ve chosen, some coding is most likely going to be required. It would be extremely beneficial to enlist the help of a developer or data engineer to assist you in reliably collecting and storing this data. Having the ability to retrieve and parse data from an API, work with S3 or a simple database, and create basic automations will come in handy throughout this process. Remember the illustration above. + +![Workflow Diagram](../static/img/ccf_on_prem_blog_img_1.png) + +You’ll want to ensure that whatever method you’ve chosen for data collection, that you’re able to perform the collection at a set timed interval. Tracking machine uptime is paramount to being successful here. + +#### Calculating upTime hours + +A number of fields in the On-premise Data Model, represent uptime hours of your agent over a period of time. As you collect uptime information about your endpoints, you will want to populate these fields and increment each individual counter. + +Each uptime counter represents a historical period of time. Daily, weekly, yearly, as well as perhaps 30, 60, 90 day increments. These fields may be present in your data source from day one, but perhaps you may need to create and calculate these. + +![Workflow Diagram](../static/img/ccf_on_prem_blog_img_2.png) + +In the diagram above is an oversimplified view of a data collection event for a single endpoint. Let's say for instance the counter you're incrementing is the **dailyUptime** counter. As data about the endpoint comes in it’s determined that the endpoint has been online in the last 1 hour. To increment the daily counter we first need to check the timestamp of when the counter was last reset. If the counter is less than 24 hours old, then we can increment the counter by 1 hour. If it is older than 24 hours we should reset the counter to 1. Additionally you should also reset the timestamp to a current date and time. + +Here is a quick sample of pseudo code to illustrate a couple of these use cases. + +**Increment Daily Uptime Counters** + +![Workflow Diagram](../static/img/ccf_on_prem_blog_img_5.png) + +**Increment 30 Day Uptime Counters** + +![Workflow Diagram](../static/img/ccf_on_prem_blog_img_6.png) + +This same principle applies to all other values related to uptime. **weeklyUptime**, **monthlyUptime**, and **yearlyUptime** can all be calculated this way. You can also add additional uptime counters as you see fit; however ensure that the required uptime fields from the On-premise Data Model are present. + +When creating the initial timestamps always remember to start the timestamp from the moment the machine was first added to the data model. It is not advisable to to create an arbitrary initial timestamp as this can cause your uptime fields to be wildly inaccurate. + +## Conclusion + +Collecting on-premise data for activities such as patching, inventory, and lifecycle management has been happening across IT organizations for a very long time. An incredible amount of work has gone into the development and deployment of tooling to achieve those goals. As the world moves more towards implementing green initiatives to better shape their technology futures, being able to calculate and report on the environmental impact of our infrastructures grows as well. + +Even though you may not have a fit for purpose tool to collect on-premise data, that doesn’t mean you can’t still get the data you need. This data most likely already exists in many other tools already in use by your organization. The Anti-Virus used in this post is clearly not meant for this task, but with a bit of trial and error, it can do exactly what is needed to gather all of the necessary data. Be creative and keep an open mind. Data exists everywhere. + +* + This paper represents an exploratory project undertaken by EA employees to explore ways to leverage existing data and automate methods to calculate electricity use and emissions associated with on-premise endpoints. The statements and opinions expressed in this article are those of the author and do not represent how EA calculates emissions and do not constitute or imply an endorsement of a product, process or service. +* \ No newline at end of file diff --git a/microsite/blog/authors.yml b/microsite/blog/authors.yml index 9282db7fd..68cee9065 100644 --- a/microsite/blog/authors.yml +++ b/microsite/blog/authors.yml @@ -4,3 +4,6 @@ asmith: url: https://github.com/4upz image_url: https://github.com/4upz.png email: arik.m.smith@gmail.com +ksambrook: + name: Kenneth Sambrook + title: Senior Software Engineer (Security) @ Electronic Arts \ No newline at end of file diff --git a/microsite/static/img/ccf_on_prem_blog_img_1.png b/microsite/static/img/ccf_on_prem_blog_img_1.png new file mode 100644 index 0000000000000000000000000000000000000000..659f42f282911eb507d414d4d3175e5bcda53b53 GIT binary patch literal 91508 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
oKnn-O_UipV|%c;bFlTh~e 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

R`q`O>j56dOKr~ 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 UeJ9_*Q{_ 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�={3^x(scGm_-0J#19)Gq|wC_=c(d;=Q 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 PRwy7%^jI% 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

Ie&&FhTBT7Ju3bdLc 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

mec|dbG&Hoj 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

6f8FiLl_hEbYGM3HFlBk#rjiL{p2 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 literal 0 HcmV?d00001 diff --git a/microsite/static/img/ccf_on_prem_blog_img_6.png b/microsite/static/img/ccf_on_prem_blog_img_6.png new file mode 100644 index 0000000000000000000000000000000000000000..d70a82697241e42ffc80a04e6129c7ebdd15dea4 GIT binary patch literal 372239 zcmcG#b980T^C)_viLHt4iEZ00Duwz0Kn#9zI`2`q|`kj$YxN(os0uSswz#-Gt~kT4X3LP|Ax zk#Lpz-@c1#ievB+yY)}xF1{EGi)&-UyY0!t9ky2a*mv5Wzdit2Ke(OG|8}?ozT+Rr z7I+q80iF^>w0*QttWB+OBX2R{0y(||5QPrl;qo&U7pVcV{x1*L=R86fXP8vM^MWrQ z)>Bv!aF77v8Wal{c*rM|3ur(Unv_{E00pysuV17DybsRBj7cc$fW{#iPLswV1nxp( znS9Wh#11T=eB@xP7LaZDldnuF4vPJdAEiy|moY2=0jvN0>1ihzrAX8U+BP4B?sf@B z@X+*4p4CJs5d^~RE^VQP=ho6A9^X-X7o553lJ(1K-v~u4Pg|J2;N~LaZOPbbuR!gZnm>c35r48n(Cmsb)6rXk^GDhVFCv{ti zu-_p#JF!2wT`UZdIE#uFMp|jbCN4Tw^F`!@)l4rYO>iuQ=8e{8E#-jHarU{YPMohn zK!R~XK`!tQT#b4>7dNgkPyYl|3v9eq`qseG>Y*QADxU#r7iRwzskBy{3=!REQt)8e z+1NGw^FgHTFxE}(oQJ)Un{hL(pjKo+RTR+#R2zAML&CGG7!+CF^8qOaRA}*j3_rSb z0@^GOQGT%SRcQ^EdKz|F;}E@=rxDybxbef!T*x~uz7`Y)sow%wH1tvOY6Ice2bW~V z>Q|eQ!Wf%BzjuQ>X|Z;D#6k4it|6{nD$Jq7JqmrP4S?Y)NqtA986|{mrwlN@q!J@l zVq#*DTA~9NkNe>1F0E7`R$bD~$B&2463Rd(6((#?p_7#U(oPXH6o>lJ$4jIE7R8CI zA^y>I@UnD)0L`LoC9!@HKXBMY9)c1SzSXak%WwDVWv9~vqy0=)FbI z)DUK67)W2^xhQao;0DecN-&5EfaeWiH?CMjg(B3_kNsU%wxQe3lBHKn_8!{xPE|H+ z8Yo*Kn=*#2ZkY2UVVeN4d8Z)JY`gqa1Pj^^RqR`q*e2hvz2oiUF9F*xxn8~<;i`U| zC~xx@tEYm1OWyf~1@}WhJno?k?Tic7A;4W?8h`BdzXSi3+^W?k>W`R zmO|lX!Ay-#B9o10AC}1fvJ`|bra*iU=eCP=4LA}pAOk8P%!#=wqtAXJh^SRY!VRa) zHJSY80iF6CK90?`|F*Ed+nJOyUvF7VH#Ib(UMVE4f zvhLCvo^n`$13E3}`Dow%a4vLXt&>iV!05Cvt2z%4|($<52{ik6M(I)>RCwTOC zIS2Ab6pvr=zrOW~iO}TytPXYvaDY26Q0V0)jW1TLr||0O_#^X|M1h2XxRTIgzR$VS4AA~a!U7|jW`8QC3`W|UP;&p1Nd_%#rGI|O3toE$>EYC5`vHme0A|fI@ zqJ1}iWhx}&?>vD*{`xW2R=qbM^BR@$#GU$Jr4bTH`g=qs@dty?t; z=7lTpbUK#h&Wx6sm!Fp5>zk~BGk%4l(=}7DQ>e#(r|gVQqyTt;@z{#;52P)=E|iSMrMT4k}a1Ii;&qF2W2#1q$u+Kr^5j!~8DIkJk4hVagz` zAhDo*+qjMvwQ6@9pD_ulyC%UDP4hPSQH9ZMKkZFx55J zVQ8{xJgs4=!!=@`E1f@XWFJVg&9v>9evZ2H#?wv5NN?dlWol(+IUsCWIZ)`|Nm=)}g|s z<5eci21R!RR7ZAWI-vz}xrz+Vy(;ZteWVruoca>TW$;s^|9`C=8f~w z9r6$KB}6F%;TN4Ra1c$90(*iS&omDO)ofO8)^0YUWX9#k zv6DQA(u>rISc`i1^7R(}B0#44B^km9+ttIm1?$1yo&{4CW{FB4pB4`rZ%s-h!7VnD znMk?_Q&yjPPM+a>U1;W^adCd5HV0lm>j2}ZXDl)n(${S7ao1fKdDx`UD^RF}5 z3$!3KY>IX1TG<3Db}H5cNvZ|%E=5=c?X+oTr^8`OGRAz|f(TW-87a#VOWkQN$>1m* z=2GYSp~LB^g`vfmRJQ$9t-Kmflh`PesGZoA6a^_o>5Z)SV4}XN{Ri_)mQofkNWC7J z9(bc{hn8yDT`C<~ue76-7PcGhWW)K|^2*bI(}+_$X3chqS6V7Q3*U*C)jz8hExw*K zo^977Pe)JC6g&xf(qkSoSBZm7vIGSM(=o6D=o)f|@#3S67{pK}aJcZUTd_W6s|xRt zufp;E%4Soc;Unk|^3z%5sk1ywM%S++ijks`{^>szQROVNtOQ)=XQ5}fW;hfi3oYdA zvy8Y+CZZ-%jaZw;b0Qv1+$C*=EohR}Pd;Lo23sOd9?El_nw)qhX zDDqX=cCNh6-E{qi9h*pLOVUj8*Cy6-cwaE;s(fy+H`?!Q6wivBr>A5{{?^$^{(Ype zU|BXYML>tcSo_Us{)%8SvKBelTUbua0f3Q0GZjNIPGd(Olf`fl4qy6cx{pp5P<5ydn zjceC3Q|qC|M_YP!19tY6=qAQeaqGPXeO`%+=6Y5S3zJOMwL$&owo&(>wd*y|L;gie zC#RGBjlg|p>)ZTG84?q6(j3`c;)yqIEglc9D&7jR1ip>kwJnds@YCdc-+m}v>>+`+ zOOlt%#8jurz>rt0Y^*zQnCGh4Q|~4-kxbFRHr(2h7t{0j^k{O13gj?>&wJv2f5{DW zh6dGvIQ1^ul{Sv2yf)oqH<0T+8|MsfTNgb>UQ|?HF7NYFAQ*tnXg+(l>g#jx3%OMd zhKj9zH-m?G1f0Q~z_rn~yJNShGmyLaI>jn|TajzkcIVaGvEb>);@sAf!(0nK? z@&z9npVw9M=2glBx8e^)+N?DG3eWxLQwPl-3k3&xS?-;2osX;f&qen@b;dJ;^AEbq zs`;#V{>&=Vs%Ha650gjp6WxcV@s65~hmF0im)FkIpsi3_q9r1bkId8U?M**pK4V$u zvcK&!*3+4tp`RfziE)R{ABst9>aQHYz#b|f84iG+1uD){I}kJzR^`7uP<-UPLL3!8cN7t3TGHn7@&MXT7zO|dh6{lDgup(l02tnX z!4hE90EmC#!2y6!O9145&?tPi|CG4T`VY^)TZn`Z0QBb_+Gq912LDfLD1mH<|AfKj zKl=c}s-n`;pRKBilbM;F^ACHM0n{g*PXw%kl(sVffJO071(Q}Gzx+p{p0G|iXCunQtVnpm=Yh&lk!JA*qbgT0dl zBQrNQHzN}ZBMS@tCk4H;r=5$D2fdv$*}sMSryMaeXA>t&2Nz3wJK}%j8X4QWy6}^d z{-fx>%D?S2^RWDHO?J-zF|5x4GXB%U$jrdR_+PR=S^56K@+et)nAvEHS=xTi*{2Qx zHclqKf6@Pcdj4DE|KQa6Z%$@bHkSX%`ain<->mA+W=^8^wx2p(1pa%y{)72{cK!z= zALBnm{~uHF?|J?g_H&{I;P@E-YtaPYjKB3ceXb+ErI@1HXZz`8|0*Y+SL)CDPkRD@ zNTacyNvH<^gaFcF!fGC1rg6mkUi_8~GMoi7RL&C(i17np$X z^^F5>tn6Jr8qUw{gy#Yw73wBhHn2KssAN!Cji;!8NKKKAo$;^1Ce7O1TLR?_*MLc{n(b{zh8`#F z2?z!^sZ$0G_Ekn_Z#jJL5bl14sOzErZu0*6jt}Ne7brCOb8GllFBV+zs#b4K_EpzI zYSY@Shp7qYynx9W>maHLfhZYX^!h#2hx(C_Rk{~bLPYUypJSf&@2NUTa7EtL zf?(gT>>aviQpi^do#boJ3@Te#S=uuAZg$fkS6n*f4L_pcUkslLxU>1`P`3R|WgT)M z@@6IDn5Tn+mWLk0R)$syIfNbvavzW?LY+3wZkF+y-%e9H=QggNATC+I-k%o6*%XDZ zVPM%Kz9aIWuaq1t$-|rq+@s5m3JS0xeRub|PL%H6POGG+GNa(k^gTKX_j;X1eY3pG zkq7tZ`{04D7vUq&s*3-C^0qJ&)JJkp!?xD#0TVst?>&Ek#HasN$-ERgR@nrfMjk?z=<@A z#2WjnL;AZGA(0UsUW=5gTByq^=$^vGcHDfWRK8A!?0d9Az{quHwRGfA-gj!_sZ{)= z1`k@>5wTVCZ6jGKQmk01qZy0P;M*>H{(; z?ufxfcX;VB5al<)lzJ@>Qxbf~EmWi-x0U1Hyk#4!JjdDju1iC$K8ZMoL;}$7;z;fD zDH(ZR1HNHEVMJ~+NeIJ1K{k7Lzra8k0lspaB4$RladNy5&^;4x0oag5T0BQZ>8HZc3tgzu!gITXcGl>T--S=}z(P2GCgwP7pcqib9LW1j&t zf{H$79Ag~AlU|Dl*TGGd=jRl^P4_(f;iy-IZW+hmq5@c(H55L~b2?tRf8WJ^06y$A zGKF9t`X+x_hZugjKO(ZQ|ZcWS)cuKepdjznM`2A2j4Y8|AO75EJO zAP;_Q{rPU#u};@ay}}LFHvZvU^!c!I8>e6FW%)**^T)y9VbeXl7%Fwu_yS114K-qX~q0jJ!+^(d>Om>_>$E(pM z*k^E`MVZcQJK2bR7>^EAeh}|H{;K>i|9nS=w@RR!=Vnx-iOAc0y8Lc7gnRbzWy{yM z@16IbH@BZB5@a3>yfS30aX*>|+0IqmfCCcLVjP6ca5gv+{twqsn_;J<$LsABeb7!#JX&z^cLF5xM-{NN z;E2ZhRLPI=rQ@J>Oea%8X=wAGZdD0&P$XbEv z7YkRPo{>W>WaK%1oetCmqorq9(V;XwS&AE%)Wx!Cub$ug(0Vy8$;_HN9gs%GUAl7Z z=6QQCg`lc{#%{`EB0PV+hWIfS5oxW#uxDEmS8Z0zg$_pen+oZVTFEcIouffk8-3lT z>t3(lDH?5jwH=9R{Jb_xb$Q8iX(|sJv1BOyTFSVI-UBnk#OK0#A}#tnX(Fw#h?>h4 z22UIRiUA(oxloKvV#ray!bT35BrAy9eBn;$4M&4af+d!pRhkW(v zLRPe+tN4w=xSLIiMJ{VlbanFHq3C&Wt9;?1nnkRY;d8Y+0nB#SnY;SPTbS(giX5b#(#kRST9uatVS{Hck zYv0?}mqj?w);%#W;u2hI2)b}P-Sozf`>Lp(rSRLnvFO`d1;SDn=pg_CoWDcL&1|?o z0ai9&HF-GyTcHYkAlOuZSnbZ_5&QByC0sRxt6BYxd^{Uz^17m+6zXpT5db$1@uxRS z>{b#7_WoYg(ez4p!KL@Qs5+nffJWkaI=W&GxDm`qt^47Jqf@)Rno*JB+}IKa{VJ!; z5d(od$y$O?{GJ)Do5Dd^R_5euBzT&!#L1|b!)lGHzC(1e4Zv|6sNLfbaCTF5+^MOM zJ1v|`t)cJ=U6l9lv3s7k!Y*2r)A#n{t6qa=JigJlcw2k*V=?6CmEagpKOtn`(lu`# z>e^_G8bpbp&`UOA6Tc@%op%fXL1xQha85Nx-qb2L`PCV%*)1#=hvqWuZByo+y5B6m z1wP|HGD9a_)dM`eF8e?Q5Gkj5Rr|I%7+Hti?pS@?O|kE7AM{?Ab^2|@FD1QA4olkU z!!Nk_wRMcUA5PvrM3VsyTC@q{xqy&!Mgb;f8CR9 zUS95s<(a-petz8LEzE9<3;ycXMJ9UuCPy}TE5)?&q92iE){WJ*M(|@}jZEncPjdaE zOVn^g5hNraiDxV*!VDIAD1$^_;?0|VXqe3#xXqheEb#(F*A2W))gz3p{wQ($f^Q1> z5~OGja5leg>udP%^63%1ztT}x+_?dNs8^ZKKsOS;ZhgMW)VY{q_M+lE;QO5cSv2Wm zL*+@ay3Em`U4~7Q*>*dG>ymtN77K_RyM^lKJYAz5ZztHPqt+b>)3|*?F_e4-`nBXT zf2A+I!EWUX+&zynfl#W zRaLzRbR`yfL+Lcw7)=rSb$(ZTS8E*d*Qw^~ws2CaGX>UXM6xDz+OE8v$gaGYuT+9% z+dU=^Lkk%F^nZ=SpX5dW(}est#a)$Y>JpSJHnt6tsDr25phh+~3os#o1$DGu3}6;= zmgb6=0$|m2^Yyi4d|*6XIxpZ~>j(h7b#+C3-0(vK1h(wAdNe2O(tIyd;jxzb?Q|4l z>6g};_6I&{?gtDjyxe|{&-S<-uawOMtRoahLq9Ys^gF8U z?xu003+I%Ot;OCrVDVL|5I!!sFJJb$5kw6`@}a91hq&GfAOwY>4fKR4vp-}$H!nWt zuV)2kQqW%j{B5N?Z%sc5X8L1Np)WHwe2fe3BsF@cI5|a=FXRyTasQG}y5|%BK41II zZWmMG=W9HdE^E`s%iB@8LQ$Y^m+osij)Es~|Eo&99ocyrT43W2BMjp!RV~)=H!W(_ zMshsRv(r*z@`0_d(Zs$p?3hXw8y>RKMIh|jxmN5lk}t+lpw%^V*Kemu>kX7EX&Hal zm2*3Vv&T|PZ)|?PdI*ly2NTp{Ke+^qcT>kH6{z-cu^$+TBUmn=<1FN&d(%=4FK4mU$D7F%->AAWf52ZrZFme+ ziR(HSPu*ylNtrE?YEktb4qo}#mq^v_9Yy}1EuQduqgY$)nIl@MwPLbdzen>CbrlsT zioGqG8g$#Zs+OL_qZ4FjfxEq=2hHy#oL68zf>LR=kOG67CbdlV154U;pzsKebXn~{ zIVfcwkwWw-MzXB4>a}W)>)ZSD!Sn1BnSq$iE3j1k+)5{>w;clf%RrDj(?0Oo^=q7g zThwVPj?l9nPapxh%e~wx4+klV_`O`2a-Hman@uXZO)AH@{CX1^WEfp{U3Kl=l+DO> zO-`N~py=|fgdIfVnJrZZ$5L+RCIO7HqLNx}W25`W$G!Ku6`=+2l9GoR%C%zI*AE$G-|pP|f_w=(MVmyd{yUI%vQD4MCJV9o0B5JP4cpDcfr&DRJm zX7D&~Zx{mYIwz}8F-tzd@+D+RYNWXbcJQ-~?`8hz0?_L{U{NoxAy7F#{MO8|rTVl+ z7A>rCUYh0d$&Ug_(y*h#{(L63n(PfcEq}HJ051+mD0Bh&)Q58-g@o5~;Vx1LW^)0$ z-zK!U1w|pEy7OT}p#5m4Rh6@m9`hSZa%&{skc7yU5jaEV+qp#ooBOqAd$oV{75prJ zi@(mEh)zNXAiXL#8@u6X>$GvU9^>Fm@8gXO#hPSSgbge{;rpxaG!Dov%I%Dz6!c9K zV{?$1W4u~4$WpQ^uzS&@=k2COn=DOw*qGmf0CrUz+~urR;G>Au>SnP$ zEx?+$@go!^S#3nXuu?m{-^ zOF(UAGprw1Stk5zVKXm&g_OA?D5afj&|ysPvZ`?hM_g!}!fpjqwyu3PQE5%z);W-) zMz*YQX7@jRw*hyON7sDML^GvI@9$@PgQ-i&-&rT%7G;yltD@?zNc+(Emq1zjVkO6J z+H-ytyrePr8qG`I_>fwbqPJ%}W{toPf`7nMwWecv_`Ze?giCUQ^cmOtdS|`Ay{6$m z9(7yrds!;QJFhaP-BAp6JnMAyG+kdiCc(GEmFusb(*k<7`S~S^(BRCr{!*_Q7x+>+NZyf2~9>BV|la^v9wL2i>H1q5B|lx$#~-wB`~CD_TPcI zCO6hUePv6$8hAIvgE)5kdyG&ge+&RY6%gg@v*VEzm>(fO8LcO;# z>}OX2BA6AfuXT>pND(-7z1yW_L3+Eceg@qZ5njX6bO+Sa$QjvQ!@>n=dFioF=ThTJ zVbz*%g7HbAe&`U(V8BoS5P;65>lRb$CZ*L!aAe7l>xZC+8#Y5mD;ZndK44*Pi)FxI zSGl-7$WlVXmfa_;E)#qdCA-GmQpREV&6Xt4+g2?0F>;)3rKIPlC_}JJ z*V;6j8IiV>ff~_BDu>Wx{dm7n#!+S(hBgpBI_mXi*ce zCGWv~kJH!iEXHPs&pkF`n80WT_T+UvUnO-tuc^{7wuQhSGZc{=8P}FJk6Y&M-;JBR z4Oei+`wZ3)Na_;X;#~jb^%W6_UR020Sf>vp4OVpcU7Mb9%$&Tky;TO6OBvQvda2Q#mV2^_bcbQnH((W z^L8C=PZefCxE}KLW#9tym63QbRXD>bl_Ie3OpgiR;#I{eKC<%3PfiLmv&Gz>i=EZ~-Enuam4ivS(krTR|MP z_lA=eV*x=A-3Q#=2PJP$M>!CJhNoS??gK8!KY&NB7?Q6!1;5R7b@Xcs_9)=O^YBq{ z*f>Rx5QK*PLUqyz=B6Q6#ok@P7}~I!Py;L}aq8KUB4}K9u`H&zI}4oKIs}>^cw#R^ zhp16G?<%L5rjDr}f?y(~%~2tIN9Ho=7$;VaNAcBam%bfDAprC+QU&V?bM$W2YAD~+ z9LPd+5iW5L-%VG=mx zS}$Mwpf#{irMrHiTXUn=|7N4{gHTzfn>}skZj#s=r3L4u3&;d4HFzbeNZB4MxKU$^ zys5QIu1QDlZx}$nRkg%?E=-YwZnuTeiEIFfv7-JSCR+gc5~mRw0L0Nr=F%+$zx4Ek z`Rd4CV`fL)bd?1T%x|gUdrz$Jquq2S*mV954*)I!x(_J256(ZJ9$&v??hP8OBj8_p zocpj4aW0Ly^+{ec42=1jHKa> zP-(@!<`3klkaFnsEW91SiOrhbS_wBF?2pzEzNOAG*dGcEXk}(e)_me0CRGw*Qx&Y0 zv4%D)B|ngv66&Q#$yYEpF8bkrD>48M9N|6yTP$O@16iXJ@j9tG~$; z13%AN+E6Jj=7c`iv%?GjV}4wzlKf9d9EnoMAni%)g3TN3R93o~)rzD|GEb0m+?xK% z*;M0PjMNb?Z-X-~cgEV?DMsl`cd=+)y`3R@YG|eBUqG;yyr~}^%k75oI_Q#u=<$_C z?lw@7^&fzB_~SgZClH9_v%VdMhz7uhnMqQ|>An?#l{Ci6ffVOb=(B$A5`%4(+N*J{ z=55q)YHUbrPze$EvSuy$wgS()L2-O@ltmzPpy?6~{eU_LCg=~;rucZmx&J)2k^_)o zqRNM9R&fX8s(OsmP$JTv%SL!48&Qj-3|+VGNpmY5_q=XLRLrC*t-+2>`8SsF9_)Js zW>?DQl2#o<{w?U(i1hdJ2MvX*kHU8ZK1s@8U3d`08?Y7}4N(EJ-nl&ImcEiD@`aLA zum$Z6eVEDO#g_B2)(6i{Mu;&)mCZVEJrb1>`Iw~4Zh3pkr}f+|##jlH3Uu&ZPgK2J zp7(S(xzYH2gxe6g!m2?(5MxZFm3e@!u7HQ~x^kd12ryO?%Ch%ex7|r~VdbonS1eLYGmrTEXI(z)>^mj>-?r#qWMyoJ; zp449=xZ3?U9@EyXCVq6j|M&PH_jqy6otW z>(6P|@!Z#6@SF_i$D6$!4>I*69}1ri*K+66qlm^)qsQ@5be(6MEve5hD_P%0@OEyC zMo!*D@3J}=wmn9h)x_l)byA$reng!+Y)svx{UsPz=QNilzDP?{RAA_hZ1a2@ej4C( zO6y?uGMRBbw_oucOQgfi8$8*kqsoj?JyD!OC1&|mTOeU5zrEVwM(Ri0&~Bk-N8 zTduA0tQ0!`gpPFD8he>mj+TB;?HLV~g)ZEqZyHQQ2p!F|EYJd*@!=Edy#*uIS zWmcvj%dpJP!a__8-|)JF?l`KDRYNDWft%V;YRbjzdpI!xDCc>~xo!Pvkd}7?Ra)40 zN(T6>{U!Jv!bz7^xxdS%bw^%b4lPS zLPE#IRq+*Fe~p)?B@xVi6s&S&sc50CE5m%rh4y-I1dNJP+q>Mh0$Q>2lJkLL?Z)M; zhR8DZdsHBRE@bQrTZ*L<1+F}(4_i|1@MTPHVuu;bGq^kj0l^V`ara!66I00+Vxs6) zC%%oS$=q<`jqzFGA*N-M2B&1K)IAKyz}U^||K{BA3-YYh1j3 zJ=ia3uNemoLZ3 zDn0^N9>or1#m+w2?zN!(7>^2OubmFTG683Qs0W8fX^^~P#!XwxMTKYuSd-zZ|9dp2 zj3Y9bR|q!#o^0g()q(6iY<-P7n+*F3LM?KM8UCW0H{CTp=8fWF+dTY;B%u)%n{Js~AMBB$|vjOt?2T`6v?i16}#oBt6>n;O# zOIGJzJnx4Z)n2NVyxhleru4>5<@Wm?(M8db;Zxg}Eh&)v=f3kQYvV-mDB-zKVY9;I z;V)aPi~jrmM48};tnuV-7&cbMIQdG6(3j-XLc|}@mFM9T?<;Msz>=~y(-NvGcE#o> zvP{E#I**$`;KjvwTd_Oxx_7kvM~f$SDyIT)TQjEWd1g27f$`iYt9-hJU8CsalJ5dE zp8oPM!`SIbf2G0`wZhom!qPEuJLfBZ06Z>A;63NIk|WQEi#fJQ9S1G05G_yZ%`dUY zxGKh$Ewbddnt;?k9}V~ZgIk8XQDv{==)qjBz==g!-eg`JrWkD*<3T&mlhhVzQ} zBlh|YubU0s4REc9F}%jxE6v8&5zWt}v^hmC|L^m2Qh_Ri<+FFg9Q)JP(`7dyRX18; z-ii<3(l+u5~cvPsrQv_3r>d%EePbTk7gUlEpJ7f~)gAyNMx)f)ggT zOLe7p1Y+>stJ4YWVJ$pk>iPRRm(zipVow{6zQTe_sK>+eo7x1IC(j!E)6L4$p~QaW z$v*YMtU*abj_%aSt;d_)bNWb`N5Jbm6kkilU10f)t^Ck~yWy3Mp}wehFz3Wkn#)3} z-e2z)BK(6HY5vs02Rnk+yDIO=qK?#y`Jj1x(6!&$QB~rNQ^E`HwF38BD)OPfufz4y z2t071!8P;9m3?C5C0w66Q|hG}Z-Jl4Gi1hDq4Icw8|!j}(EPAxN8I~1*k#kR2!b)G zFZq$GwRPiIEK#_{d;ace>54NWTlkD69CZu}M89%vwL~cRZ7EUGG~@R+7S$%HuokpD z-07F}y~lK-(_3DbOn4SE8Y-W`cP;TyW%J9n*oD=rtQ|3Icb4B&y+C$1MagOBnf5=2 zEg71n!T#-;5Fe12uoj{izlu!k04j$Ua-Xucsy>X%@ZY;qz%w`d~ogGb0B31pIckCviGRrNw zQXr3aa>dHMf+VPyQp^{%2Y13N6Bv-Zv(#URPZ}0p2w@vh%8OEH67?cu4@uL1I9}U& zD_Z01@OQy-rHlsmQK@&c9G@3>>zx;TL$iON6ff{r+HewX^*6B5y&L$@y4zcDa}@h} zvo_hNO*1r3ECY1sw9j}HeAj^I$eK`wX0!-*ncrs4B4|XVp!tRxyCmnCNI8R-oNCyx zZfP}n&R(D4QUSMpm`cW!>|6ICJG8kNFRyo-Osjn$1fzbvtt zP8gv+%6~qKQw)2#c2P33M*y%XPqO*<@&w0>;NzNH9zG$yi2c1^o}Xm3L6;Ze^b|*x z%WCw#>n!$1{S`1z(#ji>`xkE@fPo+S21Yah5@Q*_h4F0v1=N9G|2u6d!GW(dQ@TL4j4e$yK#68V z;7OQ&#D6$PCopV*Vh}_qAzo*fsnOTbh)MF-)>R6zkd~wYLtT-1(9^?C`Z2?lo)V>T zHIzp)GQ+7tcWZT>euQaqv6*Oa-j<`|rfH?T35-2(Fh(MSQ-s=|{CCaQ69!mN0^huv z*umVN_O0*-8?dNw&HdyRa%eq>P5Jbc!Q)>)`OII-$&ETt?ANkoVleaMA9EfQ>lyGw#vPs!$ zG$~}^p)N-B`qibD?-e*)T?D#R=vSp}=5Yl#ijmEhcRpN;q425sn7-min?f!Ck!sD&DKdVM#k_pxci@D*RzJkw1dq{=Tq7nG1 z1&z(ch)--KadAN2|oW!|^Wc={?? zEun=D-sJMbD@HQE1nH(cn2&}Viq3vwd330*@fr3moRk3CN9M}fFk|W6eWmebA*JzQ zyiuPlj~aEqICQGO74!g0?S zEC+lg&q%;=S_&_q&~aqzMl7=V@;m&nk8-Jp^`g?z3xaPs`r-COHP#18HLa65$s_x zS!>TR&i&W){o?%tdiE$oJbItd(>$05RE-Me`3RQ{a_JU z43L_!*UKiGQ##bjF|%_&AcTp;pq&hc2|v9rTQ_*VoTI#gVpE2fa^9i2U=W05JX2i*Hp+Od3MUxMBo_^xub>7qgei$RF>_U4in%6`T@5o96 z8Vr*plT%a##w+A)9XaI2T#ZbkQ&&350FN5^jQ2ih9u{0O z_dKQAqD}Z9+5Q}MwDI#p*!#OxApk15(1w$l29(z!>VBT3ny)hyN?@I}6Fja8 zAZwd&O0o#>=v{TqfEq+&?H7M1y*2=D}VhkPRA3#Agbz zNy_xr6$EKrMd$L$m*SnLK>c3_C(s1sa{t0HYHXqLV zNpOSv*k0cyKk8QU(0d|^EcQ~#X}^64oJclW|1lv3&PR4G)fk1pqN*!_elrA`9S5Hv z>(G47M{k4iRy$ET>5x9EL$zl3>(R^CM18`$cdva6)!XJ{8MxevY&rip89{-cqU`EQ zv?M2lWQ04B)#akr6Yrz*AOyk#TynOx${r9Z?#dDImQFNeW(X5f!@Vr&$w}50t~fgtj{6n6 zUhSChgz~M?Q)bF1^(V(y2EDqqrbE>5rE+wf-|`{#{}$NWS6DbhwexA^;7Fg((TyC| zx%%PtOUs|+mUO9m>gzpYqbeF6SGXNHzp}M*`_24|OAJd2)t`|#&58kyAc@@Xl(rn+ z`Q(jMJg{!skh;#Tgb9m3!f=|1SZuWsRk^=xEcQQt+xAneg+3b960KDxq4ICuD*2Xz zPekU-rs;a7&{vnkQy$O26P;y<43{IwO&F0fJjLbx9iKpgqnV3=(Cffd5XU^8-g82` zx4fJ%lV zCdHxg?+UnEe_pJ{SzheCjVnoHP?IR)X{C70&T;69Ti}_~UEhP7m!AoAMGXNP1cM3U z+&w|4L@sI#mq#?k)Juc;v}Nc*zmgF7&7aoqWTy2-H%KEIC-w34wl-F$YdmfZ|B5Dg zFH5vBq;-h310rDd`CJi-Edb*Os>$ZU%n$g%P+z5_!X6C*q_Tx-e}S{dX6M0^-_7m= zW>|~}X9Xbz?z;I^QaVkIvUEgm0={^T6`^Zn$JxZ&b$C6NQ#2i7&~IyWq5mSYvRc>a zqTeYo|NL>io1>>B!9SBLQ>X0u{&5?#9IG@2}Ckh)VPs?d4ZJBB3|3{e@)6y-h*GmDHE1$1#* zDBSGPhdH)TH{<*|#cH}9%#4T*hFx5=yhEiMCa-6mz%Up2=dQD}y)i$dDQ8t&Bj@(WXGYqTOl0l$d3G-qh<#4s?Vo#&i0B9RTD+>ASUmpjBHa=hG&{tJW(Z}!O#4|+rh1Qa4 zQX?9!+#v@5JrrXrTN@Xomi)*}e6740_zRO4c4GBde+T=_j?VVz`~sgnEk8Tcw)F>9 zDl$s-oUSM*sxc+SpuR|3VM17%%|z@aRKR@g>0U)+5<#q$E8i*oDoVY2EG~aX!8cK=?wEHDZ|ih&%j)ZNF1&S8(r8GOlBt*ib^DKfV3}w zo$IvLaPm+r+br_V!BN%SMcnfzmQE{q+p=yj7=-hlSy%(7>3rvjncFhxL>mYq&# z_wRXQh2d(TGkMXicb~5AbR|eYw$k-|WH9^K-pwgG*?lBx8^s`inI~zt$GYrr7<=*Iphg38{ND(3tj|pFqK?KHtAE{1C9_G=a1Hl; z`<~0}eBG(CToKs!TR@O}71%woG71iT&~)O=awf$`JyDHj9klx~^~G;z$)==|;(Cb~ zT!?#>h_?gm`HJqv+S#_zOF5PdR(OBzEl{a&WG}X&UlrPtTw);PGX?w4 zgFks--Fe+|glnvdG?5rxLt99ge}=4VcCCDBkqN+j{17jnI0HZ_-YE*eI5h$|HN`2m zXzO1&YLx&0W`p_)q2~SmFoHVl7_ewD2%$oOmv;cJvjR6clu#Q# zaR1NoH$l^w`HNDw5I<+1x!#%?$UjLw2SGY?U#nry)Y191ysy=u=d%u7z6%`V)H>Mb zLRd2I5@~GaQX}& zIr{iiCk;iIkHx&xNlQ4_@-*C=>ZDOPArPyNUfLbQ1DFbHopBkK69s`GofJ(98Asql zQ?^Bgk}cFVl}^gD6LoONYv>|5$O#8Rohd>ED-uvpCS-Pjy)lpd;yH3X+v(nJLY4;P z%#qy$g^&Si&^Jqs>>~O}sQnJ$^CE1W@GM_jb-G)oK%+lOz07hdwnRD(141Bw`ye!$ zvLS)WP*6^I7%DC~zOv?BX)e}%uXShj8s!Y&ZB4vIPpq?N)1L#t2X1JJP7n6)K%(Hr zngrN#HX?QJ7U-%j<6OKHNyfZB#LKA@9A}7R3o=1ey{Q>u210+~I&L{kz8C?_xwmBi zH>DfWwB7080<)YQqi)Oucql-}IV@PPksiGk2;vyjc3pF$=Quk4Mi7T=nI8k60zCn% zC$>z!LZ{juCvo-KpY3V_BqC*##D2uMco>h?u!m>%<{3!SsZ zwcf9YlJ18AG}vPkJi8fmZtby>V?|_UE}OL!`^OBjHAR+6GCX1rA6qQIyvM%nQj@%U zcL6|!Q@Im#HiG8-1b=rN8>5>}Ghdp>nRB;awA@}OaPZeUv=>gFTOQpan@-)^pbD;F z#W<3~sLvkd>?U+YIe|jhO(FJ_5P)S!ASLri6cpAVy9qfUyQWdObT*T0ry4q@?4}x< zN)t!okoE8646-Jl^JGjJIg@Ap$cf|}$>{fP9dIX}oRRZSz`SHl7n=#YNwyQ~p>{_d zW9$RTn!7h_3(xA(fIQtT%zBp2mYzdzo27gR7)B1gO}d*LSh~*r`r}*E!vo7(42`uY zXQ9^O4eRjcinZ8rfdiPMPA7VEEk=?#i)KChcLuwofL=5!8yFI1!Vum56T&W(T+=+& zUe@p6fCV?{L3K(}-GG@~Lsc@Ya9KL)Q};QA4l}jwi}QfkoR6)SJrVhYQ^2!J6ks#N zi7|52lO2VuiDN6rnAc$zi5oX#S5}U*qXf=lOEs%it6;;(j=IJ6sblvx#xd+D99P+# zrKSIt(1VeuS)41`M3_t_&q;RAN1U?bi4HvM!E3~h|6q@&LO@x%c><-Cjjo4}`< z|BabXBi3#d9b;}~)z&}y2zq2oi^%M_7nv=rs6#o#>dxP+y8Er2&^UFwG9)q^9fk(i zT^vYnKQt1}Ap{$gE+Tst&;u71(IGPqpFtHd)H>^v8Q8y*o^(n;$t}83pDY^mBy=VJ zcsmFF3E-0lfN>tgzzk)VP{0;#W4UM#mZ+t+z-EpT#`?t=(#FQ5$15TbM@CS8gc31I z@Nnt8i>D>c2A)R3#T7Am`;Q_%Sw#-%#;aW@TXqre-O?5Y>Y^5VNXzu~p~;qm@)(*0n+N!O z@Z|&Op{+kyzz}a?h_@}tf@$vV*~OoUK>DBW0Y1;{kV_wMvJ37+sZ#ITp&Av7AAMnq zkG`<-))SjY2+y&%iwZFmAAxcr0IM)c-_+9{q=3DE&!HkdZ#>}hZ?q2l>#M&9KD9^% z8k1%T4~ylpZ9G|Zw6sEW6?;I2^r#HtbA(5|O%$AHgaC;mjGbqic_fdK7xMvljTfVP z3qW>zYMmP3@==9|Q|O>Vw@@}juJr#sLktjk%u{n47)2XwHmejkPdi$ore6-wTmYWD zCC>QZ9PW@}D~}V=N&&#EZlenDc!bh-?7B)T+HRjnvxCc)jZkF8#|6>KhRv~|h>t|L zb6#&F(Izf$Wd~!nc}v%5()j{-tHLN_5kNN87>r7}X71`f1ayJ&I%;8LUhd;1+J9x2 z2Y8|I5S;_qT(sE$XgV;$)5HK7=d7)*%-iPbJkfP?c(+N}WneRG;)>4F#v)@uX_eZv z$DDsebV(aP_v#Sf{s1~X0eD1dZ>o+Vn_ptqavM)9w@&IXX*P?b0V{$|G{bX}O{G+7 zLUldR6D35RJOQ2qqDT9Hye=g&#sg=J8gocv#IR6w#Ry}eb@Pf2+S}H;+O^%Ymc2Ub z+V*J|W12Y>q(M#yc@xRm(k7tR){-p}1z18sDIjD-*~zAGDZR9_494J zBO<#-M9L|K-u_J=8eo{|*?5P%p^6O1PsTdWX1apy1vbg1#b@wTcHw&mubC9B7(yPkUme=nHr>d3MXL-JNoiO_sio%SXLF=n%<&2! zPc%pzO0X_tL|zH3j$-5~qE#T0)_@d1DGyyd-->Wnl+NozV^NfXz)+$jP=+r5_QI~d zerZ?RM?BA#o${A6CPCxP8|HUE`@xzUE4oEdO|0kmOp2g03V{PS$moeM=b|VzVT`F3 z<7ngNY->Dh`ihp2!5N`@;_4?v)@z!TR`iuS)0Fs(rnC`zr&VlBrm(D4h`u%WN4S zi*EIE>nm20F~;6IFAzka67$FHnyiU2#>@~;`H%=C&5|GhLI4md1b7w!Y?Hzln*g7C z?}+#uQ@CAgqW~2*KD9-fnXC{sBg#a%$o;_;*z1|7p78{lyG(L{*379B{DNqhkFEIHillYcRZzD!T0+z{BTw{ zg@1aYU+z^#dSPkZ?G=j;zp#0f*Ev9WDaJE&=Ivj-vb)1?D6c@eFni2ibqEFQVvw;x zVclX24=uU$$mV$7AI&D1qX}DKY@y}}&f`Oh=+khJv1wf2GrqG9a<{Z;@yYj==2E10 zC+u^+S3*hdwTLc+B&P#Jg#}X(uOaq*sn}umija8Y_9JAuqVjLf?D}}yZccXW>imqZ z!9zQ_9U|Y@OXt?yUa0+Wev!Eez#!l;&nBB-FNy%JM3C1m1A%k96n`w)5qsm1XnaTf zEd5{7O?$fL?DXi?UIGmNjmYiq?dtN%c1}C?>U0F*h+ClBIQx|5X^FM5g41FhbaxF3 z5FtG#LP)P6McqoiA`2r#%f1PG9v+?AUuYip7l6;_0H3}B(tgN%?-DuM!ErIh@Rxj-ktkzvOgWSO z?IHRVdj*cpSLilR4{P>}4zOL91LsyWQP+Uw3mZ-c&Mj@)0s#lhM9?k)!sOVK6oD;p zjv;bV?ng)gx{Lr%>A@Yq^+xq#q66R9{&9uqVD?HrF8%4vUBz$+2J*`mUHpV`e_{om z>Blst`f6#$J}RJFjBZ@xhH>MVX!uk6=Ea$P`|`|okE=wQ@I28A1XBI8%WDi!K8nve zeI`hGgr%jD(`UBOYsvs;o<0*sr+KZ+U>28X=>yCBW+oPmx4 zG(d-uJ}SMm3J|i7jMKB*KCD{vO+cB9qW~ERvYW!}Q_C2{Ti8tx0ny4lLofj6L`ulf zI)RdhGj1L^Uzi!1l~4C2rt z%+n70fk+XbVS*bXfP!J7!KB|fr-$|qIdTO?cJG{?czp^#so{EYxM!C*|CQu*)dJiG zOnNg|FpvVMos2U(hwi^LYY(s!AM%<4>DL%#Bf2@t6Y&moI6 zyE!{Xy#N3}07*naR3|hY1IT7#B5JG-_=ykt{)iFfDS6{;8A_0q3OHIP`nMddQ(Gn^ zXAWbNQ_y88bZ;E+ITY|os=L;^K+nHo&-@tp6ujs^al$i_J}s?Dz?d@AT%;b)cYqM>!z-trzrn9j=f>MB*e|MN zz<7Hh`vr1F&31pSLwliY(1nMe+rri-fa7^UUe9h4h~r6r_6Ek%MSpEMyNQT@P`z-5 zKmjO%P8kJ2i?Wv>5-fm=)-4>LY!f& zzXj|jWX#rT32>g^1DwcH+etijGDfeQUou9vlN?FTZgRF0_WyP5-fp@UN`HUwMHels zAfP1cjQNSqHn)M^wz^;?t~b@)1lZ|%Oi@Sr=Je2W9dvLaL+x9D!1E)!I^Kg00L%f+ zCu{KzXT+O8w=!*$bDPcvR>bfU<)2omdoMd!n2DTrvVXPLd!>Nb$Ap>G(c7RlV0z`^ z(hhM99PM7&=`l_S%TkL!Z@q8<5D^*b)&nwzj1KLEY1a1)dr$;9ISs}b5P5%B8#?M> zdxelSDFTpE0$??6JwHAz@LHK2%IX+mM^(?yoeg#R>cB4Wa$Zx?s!B9_g-j;dQFAoe zbG$^J&XZ9(kp8btpMq6bHCJjVb&W9Y$=e;4>DQO;3ynbpIhVP|3UztWHJxzuh*LZa<`_Mn$#({qT zd`jtJry{gDoGvoq)*PkhiGp0r+e2uBNRmPlr4pqf%EnF4UW62i0fLHohX5wU2M_8H zZ;ODpB&#hU&l|~&y>|hh*HS)snu;=ovU7kQMCHswWx%url)Z&q)E0rd1sb{ZT?8e& zjOTF&gi-CMAyGnuCLa6-ix}_E-X6di-L>{GfE(Y-YLU zvOWBA%XKAPe>m?tl4dcgl_uES-lHoiHRt~P)B`>l^AXbW68fbhtlst@|F_?X|7C4B z4?hF=q|@o_1o#Bx$pL(V=wK-5Iq|&ZVF!dsNNFHV^{v3CB0eQ~?+@+tQWm_fu5(LE z@NU!5>c$-2%Yfxj7P5dfQi|InJk&#l&lAxg#T_9mZ>fZ#GVnx-r0WA*_fXvX(C!`z z!wv?*4u$mbT6Wmv6Fmri`*X!=)$m%!8G_Rt|^VzlSVKD5W;CJa@UEKQ2iVvtG zw7f-W0HWOR;EP;@8_UL}7onJV8#vuhY34G9uR#>f5EKL$3=&-tM8TGE78!S;wnayT z`B%>XC7;{XzUZYLR7Z9|mh&O8_YHu-NP#ZaP@>2N1mL+v^uoqs!RF+I2;q&!xQoG0 zFM&%5Ie?VnH2fDSTHyv|wgO$B01jSO9k_6O1AvBdka_S2%RT&q&8ndn&0jWiK_cPk zxXrmoL<$Y@#wx0FB;yi(I~t&XbeIpMmn-6BKq|n1G_QV$^jxNf6hcveD6BT1k|(IQ z?T5pb>jDB3$~KqxJ@q*3tsK8nB6V`r8~A7Y$E#=AgT<_12G zEmEZC5kJ~Edw`x@(gw80MOY7E5bBULcQ-+~2?2e~QLIFef^3k^jFlUr1lz$;~AegXKg8x>X$zz*We|Df~!u5t{Wmh3-AnM zP=z4zf108rr-zF$A~GgMTJ`!TpF3K8@Dadg(q^MAh&4gY(~IZd+2xDx;O_x*m9@|; zL1ky8fAoiS`{>JcTYosm8YG%7K+7bkd3Upls#w+K<)@|d93}A6!1U_@Dqv< z!5?A1Mzx0#@%b*`lQ3&5P&c)>^|{S%evT0qv{iwh7-b&t$vk!QN~u%w^t1mWKSzKN zeIjl9^(!I-FL5-Ux;^)RTB$d8q~W0GDIz710=Rwjxuf!iYO$f1%fZ6>OLWcvJ^u=k zQE|L)yXOs8X5hu;*zkAV9|2Oi1$xHm+W#1?VHJ}xKh3oQNRI8oC?3JxkSp>2C{ z0C2r?Yv1hM+Ud2NZ@S{LvPdmtAMxGPT+=W3m}8p3#~2FRnB1 z37rvN5e zReoei_q`{!44|)W=6{41*ysCgfKN!T0tO8ua*X^zu<<-4ph+bpaw!TJgxlJ}q|~?B zk1D5>_Bq|LOV-1|MVn@hZM&d^RTz+L4xOTeZZWsGDOQ^_IGNy*YHuE$+AE@Azh=L9iUa$Kxqy@GYy<&(GS2MUW5CbH zHaE{+kqHx#Dd1D%VJtXf0_?FNWUEr)q|+!GOTcFU8q(GM0O0e_0H1$;dTc-JUA(?D zx=^6FWHEJyU%?TGjCe$y&_BS($J7wc;N({txym|dE)Ch|21=!bcR}_-$-EH6ai|PD(B}kc2lW4DCRNXwTaN2;dVZ&GVfL`|j1Hqb5NEgD_FQ=)ei0YUgmUN(UA# z;6+|Q|6OE5Ow0O*pH2_v)kU2OfJN>O73qZzJU|C-(_~?Hq5~fuUfA_*)9Jwt^kAih z-yiJfA)LjD)lZ3tXZ+LEI%sQnSxR3i2 z{i8SBGkO;fh^&>gg%#C|J`*Fdb`^bQ6Mg0ZG>?&-gwF}~b(MEeq_~%0EPz=qGxQm1 zG86LRbeS7Kfg3=9bJ7WqZWKA&wo4k?QI#Jm5G}?z*H)ZP`Uu^0c@-mGIw_Q7_Ni`c z2fefn?~!PLWh@VD45p7~r0W$L5*a0ZRC;L*+hU(A3+biXyZqc3@O>iFImakQM#t>3 zC2$TT=1GBEBwz%)=>fXhJlP#_qMxJe;j*2E01y3%?Ihq+sim@;M$8?}mjp6C9!17C z_ug9LbLT2%gY|x>i?j3SrHpY) zV?`z>c!tq{#){jEi8)4Z(sR__G^UINmb;5yRCbed9Pi;cCYa=sdlg-ygSEAgCPx(D zvyATTY?*bOLIOUqV*~;SAeSv8y+Y%OO0NQ#Q8Zt067ebEvqn(E4|@Qg%-^>Ip9+BS zY?rHaVv5y+v8 zq;2NQOK1*WsuQ;0x$Eyd#hUC)V~+#hxADV8N*PwSmtvdxd*$o1fSm*(AP0EB=Lf@at8DR=8BM}*+Xx`U$lVp^B^5jgA>r?emkKde$~$edWglIb~HAc$jeeaXtyf}AJndrsqqW@PlT?p>)a zY$^meW7EE+-}8J4tZ-R!b?V|@uuq&|vmxis>CkyhWZwydr~`n<&STCif_`R>@=e&k zMVw?MFq1_xE@j6p;{=smsL~z^LXi~KKg!xuedR$D{TAG$-?$j)7M%7An-hu;Ut2n+NH4ni-z1K>S<`4W1CbL|@YDQf4V0(v{P={%*d(0`XV zS8M@!S|Xce79Ci6aB!jnyHY-Xrw0d!1a~?xuc7eJm1~g>tn8wHC)Hi7^A7Z1Y#o&e zlpR^bxm{kyNkW6xO=uy89#99Ay!am?Kfkl%(`)qyC>CcA6y3rm)VYy0%Uf7$`rTdRH(^m#9}a!z9P0j1ouM4nnxaW-XZCpT6;&tX9bTdUWI_tyr5YW+O?HMTL(p7BIUKn1TQid2>3)GP-6oD z*=bS32!S7v!A%NXfM~ABu#WP1hrx}3S3S40!+kqNSe+dmQj2JSLIjZl+j`TL=tO}w zyVt;{6eIz7>lCe3aefc@tO0y-uKzOuK97m`6!3{UcEKXX8T#HGDk_}^q$@&&#dT6P zNF_o)Ab7#m0UKjPj+L&0BC6Uvvk0dmG!sO5e_O=oTY*o){>xXiVlf#UCozZ5ReoPkT&iybhJxnv{!p}2j#Zv`W|28|Orh{g((V#v)>XHZ@jjLfA)rTZ}+MMljq2NjA>>Hu0GK!E_dnDrrT z8UkQdXD&fHNs#(*9eKZ~FL;NCR*Kjm8^Iyz#B~7MFrB0$3mB%3IJiOuGbl@C(_?)r_Zf+ z^1^OUUfHV)jA3lDovVPg!lZpKk=h64iBuD)yDDH8P&dn_6T&kY!UGz>Ti8)lElvb^ zrNd}qNg@W4q(22mcvCNriMl)n96V)nxw1ndp%0*Xkh*&)&ohs{V$G9w%(*wSA$tJ$ zIfI9LKpMUy()fCW`uF2_0jbX@cvUIQQTP_X@N4r-n!PY-j3_ijEM(yQ3?A(0SZjaZ z)mrLR_wD=L3cym$z9pKj+NCxWHQ7=a3K@*AWt8WYrMxYdbCxGsJjwML5lT*v+MOZJ zjp@^q;!flwe~&ALB7`x0dyJw-ZO00A$4GHLJh-$2N;F)Ol6L1(@t6Xn*ChpfKKROk zpNn{aHw5rPWunf)%+WseTnCWqw2qjZDr}CFq0|5ihZJH_7{f&kc)}<}y;~H9;~l#u zQnzxjZ3mTz?N?@OznZooQS*yijD?iJWnQE3F6Ia@p3(Ke22xWE;0J9pl z^WFkP)Fy`I$6qenCx2MBjR#~PB>O}q)u3CuJwCGCZ(iBXH`JipmmvjDxb_XecaP|l z(EKBdl|CZrTH;oH^ z_46MtVu-X;>cJ*a*cX-*-3@TQRw81E^eGH5rA%Y;5*0x0KITV@G;SGSs>QASH_ZMU zMKO&*QPo-o_za=&g`vhj0zP>jxwVgJc1J|)8qpb~-Yww4UO^=JCw4((`2|44WgUetOzII0DDpUvoP%XvO{pUGsDP{( z-dOwWTWg&3TK;CX$*>A{NZ_bIR!pfpZp2n^G_(PuYu1jz-Ra3DJ3fY41H_I zXH`H?5BNL*_#Dm=$qMi`v#N;3S$hKLCg5|ENN7bR580OowY@hYmns0C=xhrBpBT|I zfS=1md3=F}{Ko}F!2+nT@2TG7fIYCURBRkseWHexve_YD}~9g3GL|u zdY~YCQ@4&$Te&0piD(vNtenM4X=Qh0{=Po?L>6|d+eAz4lJ-VpfC>%DZfKx$Llizy z+o}0=BKOxVSHK7&3Zl$DP-cz>ls--EVbUVFHu4n%NtDI#66hIh0(_F{u82<$`1F8~ z`zy;Rb5Y>{pYT7ov}TXs<5%$iNrAEffMp{hamMJqU4Yvr`(`tcMwcL(1sx;B-)Hn0 z999i72kHWf0JNeh>T#ntCy9Cq4Qdu>p0eKqm?39gy*#mJ&j4V5II$|VQaeN@_0lCv z!Pk{#bl?>t&v-4c7W3%9MYbS0a8(bnqgvfqQ6Zvt1L(lc`Ghkt%r&&u$3!Vg|2;(y zKEok&PE@3H;0r?PZr`W{7C>%9OYoHSl->Z$M#uPwhEPvfQya+EXzS3r*Uup2F?;KG z`wb%Eo3?*JEx7KRPwt+RZ+QbKJ&9&fG;4Zcg)~G;Uclp}5)Kdy^)+5qmSC#SNT=ye z^qCHkkSaOj=`#XL`lw9DI6IH6eubRD$s%3m@Cf-s(a{b%X(v!5vb1FLL|#dse}rDT zvWDJ^~Nwww6r4z(IXuywoHLF9i!S<8=@ZIF=~7& z)%{-sKFI`{0-xA3%Ve*q#%7lJB3ni^GVg5}1~(*|MD+|cccR=&3^Xq)kcU|)8%A?R zz|sLWjBJ_XeF7^6*vq*M>lZeONL#XbWY|2itc1>-P2-5o6dWaqc2w>P^<;&csQCo6AE^zKmdu6Y3nbz>(NJi`mES;|7m3;in{0&w-*WP|1x zu|el36Grx1PPP+i??q&@%9Y5mD(8{2n~-bTuSVERNKl?X{6@z1!q`p7$OyWBCPD-* zQMeImx;M!pyFHQZ^olH#JEGpkhA+t~eX@`M1fA^!FtrGlurjfoX1Q(z2cKk&K!4TO z?qD~ykuw4yr*bC2bDzqY#`TF^om}8JL(U*;j?Zp+!YpU@u`afq?9g~_g>qWUmYG%E zdg`nb{k&8r^_=il{8p|sLzKOu%iQjEur-i7V}ZG(^9O}x2s|ecCOl^D0MN;vsb3x- z;|@ugCVTa?&U&od88W;@3i=?H!`VW~7cyg)R+nvswYWgH@e<{3=F$MI)ME?w;USq8 zBi6rDfzT0Ej!cpM!9ktB*IB2m`zm$y!AcyR0)SFl=Hxa)Bt0NJo>H9*I6(cSY@H zgS5_!qgH_0E);4mVxSf9+@{%pa9T3yq|6|&t3s&X(%h&9ypbd|WOkj7$Ln|P#8R8FTuu~gqqa9t5)h(RYMOZdXV(FFb10_g)%gQH&`@6;8#yyvZ=nb>KW;^ zC~13_UE9A>A^A6zHTgh3OyEPq2(zSwzg=P#ex#jEFbdd3EhL2*hww6iv_8>vJAD1zdkH2a4L$B&^cnyknR zoMtmk$oq-{)}zC7zk$I*9mH-JI1d0)(G>z)Gi-txHs2T<=@6l)H0BO3@{JnYXr3`L@5F$ z?L9xTeW*HM(|!1rMFBpu8`LYg2R=!28&dzJM#KjxJ8JmU!lT(rE?O(KyelMZMp0=Ues zJ_5|8qy=Ry7F}u~z!}dYpr$`GE~?PEvf2pS9o|3{yv~vuoyDL?p)jhpUkLC1kf^f? zM&1e0mnSHh=NC7|vUf$L8-UF*QW#$YpQL9w;4=ic!#qT>)H72mk?OQ|n4BHzQwpdb z#c-}r1940-dU>f6Y@|ty5<*#tHr=+y*-LAj16b3(?>Tnu^Aj2mkV01ggea6}AQZNE z5nThqI4a~awnRko5K;`_7I~Rxhi7q4$T>7KEMyKb6*qalo3W zpHX-01_hAf=PV6Jf=7SN!$lpT{3#+9+!Zj)OA}PRD;YkHjmR-=NWSQ|FQQTTx}-V_ID`@36O*&atG=6<7 zH))ejt}U&~uK=HI&eAaUGFol~CQLF@;@oZ8m1ke_iA+{!7ArDw_C;R02HpSvLfY1@rygcB_ z1;ye8_x-!8*+T&ynq2No+UK!IK+-iE!KalKX%}pAN( zH-HL0u+=OK7Rgyz74}vI3D9!U`gFjJ?kCf-Kmn=`*qK#)j0Jo%zcK~9P~BMC_)vHF zU_=Hn{tt8BMmW25mbTen4WO*Sx<$>k_SP{#rxDni&O6}qRvw>!0Qg*qV7J11a?5)n zMrD<0PTs^!-WfAB0MH<{M$F%Yg+{XuC|SmnxSWISPLtya!!AO07(gxM<*an!KkKf; z`T|fse4Q}c8;53Z1D2gPW-8Rcb?kcjX=EjlUZw`Vk9;^lZKC>Db#SYOpQUPGRv8wj zOZ9I!%AN`!jZ9YHYcKD+mzuk9hSwN%aQ+OyMIctTVOIvQuKRE@%nM)v6PkNu!la7J z?rDzKE1CO*U+fhuYSJ)pGn+JVIT|d5k`fwsE(??HcllRe*+j7Kpfa%PoZ&fHK~U3B z*9-v&tDYIAFqNy^E%lP>nVU=G`K?}7r>1cO3`78+BN4zM&iq@hP1Q8&Lvnnkx;_)z zoc$!(l>t64sOuX5pOQ$v1wN;_Hee%a1z~zQX~-oSuHr4#7q zy}=LA>ju1sSmB~3ZNQ|>G3ZoXn%+fOfI8>ws^6SmMNBIIyKA1nJ^H4JCD>Q^536q) z5pvBR+0rk2KGfsLC{n4VEUI}QJ1402oCw@v(>RdZ98Fm&eMd02QY z_5oP*;#u6sdunRzM};J1R1y!xGxH{K&cZ?frBxA|8eOI8))eZgUdFCStP{3NFT+y; zZuHQ{44F3ljJF@qhY+hNdYLKS`#uIue;W9tHi^KTx4jJ4O`9I3b+-gwb*~vpMFL>B zUKsua`lNbN-*ah~hJxwc*CwT2<^UEwXIhRv;M_CJXMrsk`d}_Vwa@P8ZLJq3Q)U6YA+dajnJP0vWZ-!VREn(1=@bC zhX&#HGqb%CJre^R^-aBemf^_2+rCMmx_;P~H-L1uZ{oaYvIn&2n`-nO7wj7qdM5Qw zUc&X_^i5J}^`azgl@hjZx~2|K0MfrYQt!n3(j=E*LUQ3w?=%b`uRTVqS7aP47drb) z-jtgGdXYZ%8i8`$gY-kRkzs}& zi>9B!tcXTaFfOOXx&$IM4ThEY=K;a_uu9AE*C?Y_td)bAka@W!f-PEkp`IH;gLs-TiUP4mfN;Wyy(OYTdb1U}nLNF5w%4j0~mW5|{t zPUBC<6mgNM37}#~3^h%(?F|AO00vo^RN)+y-bn` zx!oPs;@sMlnR_r}Cg%p3D!}+;`T6;;kX)vJ*)rK@c*wy9O!BWz`94Y}?Jzz&omDsO zy5k3R*ZAKx&R?nb>Rdm(_ewvNOhkTqN(H7a>BtM1#N7@|g85D%1Avhd3p3^STW_#2 z*d?C@_=M3W19I5kcB*#6L3~RAP=yIugaw3h-7cS|Mm&E%J}5!{jh`aFhr-=Ykzd$f zH-(+&CcnF3Qc%YC@eetuI85S#ZycKkEJyYjpW}KRF)Pzy^N_VX#_NS`=4QDl`V;~q z^JEo$S5zleCWqIo77P(3h(`p{ajlZIf!r97fYm}mQAi+dc)i8iwm~x)rj0pI$Xo_u z#tAc|2Zo~TAVz5R?nQm}y&FezXYCk}EK9arL*i0pT6fJVGnvY{hs?Qe(iB;eK25@M zlTVpgZYt24r9IyJ1Egd(Y?N&pf2PU4n3;VrZ`aHcQ&WndC@ai8>A>Hzn#V(kY2*dX zY#!E7z7eKSz<|1+Afyo%pj(87*2|`^bqgyiv!5`Bctakp?@~CqOl$6mF$>?p`oL2J z32KM&34;|twvIPv{d^ym3G7K9#Yf+qx$MJ-j;V%~CSwKRCbcr3YngNk-_R6=S`;6a z(LO#GOqqi;4Kd*PTD@cPLnL^Dy1=AN>k;$;RHyJT=BY-CCv##qjE5no> zdy6-xW)~*ZVWNoijnF9fl(}+G16q!m2an@s^)ac#v-~?R3G%#me!zqrO#D}`kIgG8 z-yUU;`QA8Fmh5 zX*{uLEPVjN{=qSt3Rtl0qheAhT+j=%hE0WT{?~!eTfzwY2Whrg_Ny2Q45GAT%>bp2 z4^pgN_DTX6`Y@gEWseEctObx{23a0lp{g(%b?#Qu`{w90Z!Ik(!#SR>PPS#XGlV2v;*h3q@)aI`CE#{lGQ8J z7B>JHeg=s>3=;d;ljEbv?2!14%rT5)fYp!hZXaoH z#Up@8Rt?`t4}-+O6dvT$QKZYroaHsfy?C8PcE7oAo3O)|dnU~=v4{;v0|{&h*5y6U z&xh2&x zJBXt(HR_P_WCqzR64oM?2Y`?@ z6cBX=klM?~013w#+}qE9A!3_zSbnr8iBq%1S-OQE?(x|*LyxGbdPecD1@yc(YL?en z53pmQj_Cuy5R0}KCf8L#!`QsJJSQxa`}P`eug+ki;p1#V1r%ecU-MG|-d>*C!T5FY z%&c$e>?K?fxs$^2bmr^T$JI^}3%syZ_ zlbi$$;iE=BlI)HTqr4b+ns9$9Od!uN-xR?Y zcAn2kpVb^dV;?}ayirx#+MvX{zUhL1yF7%fbKiu?(>M9q2PV0JwPKtv&MN-wws+dc zTU-EB+pYrO+3@U?-Dl?LpJK2s)i({WyzHm7Lh5dxIpzK9?etFcPfl2ggX}TG+>3*{ z7a@O0A}tnu2~vgnrUMj3C!A$@z`S$pAIhi&+|*6cd2E8_A7HP6>XG5m3=QxEc0poP z%R9TxXHvVmGKJDLBoQgPVDS=!q@k6EP$CVK`{*nB*>5zsaNtqfzB{*08SZRucUFSI zTE#zNn&CONRq8|tKlkSc%)_~;nTB;JaL`L15;mSlDMnCkfv$~+V^2N&2QTp0oD^6~dE1PX)+oF1KL!Q>s8#;cwEqhJ2C zQay5i(05zkV#0zi$ss7g9xYRIK+42!YSJ{R{hffsA#9iiQ8H+`s|gs4kmAzM%z2S8 zpjOoEIPRM>=Nh3@4F3TYRRd4D#yd)_PE8v@!LWqC$P=jcixCDgP4-Xf+1oM%9DvVf zI|Qoj7Vs-4`-1`7#~;F?sIif`v$L7_)HFTuk{MfC#G4xHH}*`*GYk0F^GNBlTb%6> z!%`WueR^%SPHScxu=?BMd`yw6 z_;_VbPVCBI_FHU+ObH)|8j{@pSR5anVQANwv6AlB3%D4>pu`7=(JXzO@p(#8uZi9R zba`Ou6-Dz28NEbO>lG&>Dnk*Bg7{_ zJB^u>3!Iy{#kwf~Bqf{XW%9;sQm|bim5neej6j7}IyaXo%{oC403%_1Fh{2GQ<#C$ zO)8jfm>>*N55+?b6WQXW zt|?~TJDWcK>3q&V<@r?9YEmvw3J=mm+2LqO4%x;G*$J~ql$5 zpG?+5$jHM?KGDt5uQR#AAihG9W43$?UEg8Sym@{mxM5^7lFo$j&XnXX%p@qxfnP~H z#}Dl(NzLoXwa(2BvN!r+vtLBQe&^R099u#XIt(+4PzTmL5-rjZDBSSV;(0F#8OkA{ zIVV-IM9OoW^l+^kWN|%WjYJ4Qg0&fBlk`wXd$?8=$Wy||KTU8=_5c|U089<|~uj+yYeM?V9?| zG^fAEJCFC0!m$H|0nO<9l*1urlG~97d%fw&_ zIr!{KH0q@^Qf|V^l<=Ipaw99_p5@@~#}1=^oT($g^NHxYB~pEdO! zqZ@I}3w^229_>Pbfe+eMX~~FdOTKW)n8n6^u_S%jGTnRLSzW-d#7~x|Pr1 zu{kg~k3l_B?x>gM0HzZ^W3x<=Uqaql$YK3KvNuYG6IR{J;Eq9zht(e&M0SWZ4b8+P zY&V}6*%a;aQo6`|+T*i@WLEYAfiMe39i&m%dSFR>^~it9!EeO)1pMrQE!2YtX@C`C z|BNyCiL&~2b`&^sId`kM1fL(*@qUQ{8WuByi#Ey8N{PMgVzL9>Phgjlzuy-eh< zlE#J1Swoxy>_s*hkZf+@eGE%Q?vqWP-_sv6@i~gY<%ec`1ygzU(9uAPK}idrP?&_A z6NSC!7LYh9)EJbt%>mTH^BVQden%Q?gHz=%H~<;f5LBKzx~20pRvffX)q z&Rcx*D*Q0VW(@FioM8<03n0#9f6Fchz|#3PY^)tXyb6AF)RDo(nCWBn0ASd3S?z<^-PY2xr9$&g_`C9a7gUpxat{JoE{;B+C()?uub~$ z>5)0H*XR~9bgX@U)KIW4 zX}A^Y5oU!LjE6OBW_cJ=UM4rOs(_(%@X8!w!?1lZWOgpbZP?K4y>Z(&sdoxf&-C)T zv|1I^CjckuB-UH>PbJusMf#>P{gaP9cbqzHjNWM!&r=9+*e20Cp)6_tX2$0`;B%Zo zZqPw(QpZb^RVkdCxkjg0Lx4$s#|5fBSjng_Qq;b? zFAmJ!OX@{JDbE?)m-}W+wQt%?qv~2+ikk;ZgRso-numeq2jKI=jLoL=G~_%NWCcoR zR8}$5gwfn$z%nw7_dY6^;X(QDGZd+3sUPX_DjBo2euP>hVfJ9v$tFb#77a$~9OBl> zL1CegHY9hJ`-*xQ7r-Zng6(C3fKO5Z+^3IGFB5ZBJ`?Z>K&0!2)q?S>Yo<;wQ>M4b zaK5D2#B}Z09!9+k3bZhFqL`ZMV|uy1g8`jfII;9W(emCVs-R)*qY7@- zfV_^{O#5gJrn6`AMdXkL~7b}|^s+97cnTw}!Dg(G!kxQ-1anch z>F_rw4_%y{Zj#xhq6x!(kI+X3xpo|YdpGR;H(YZW{6+Vuhxbv#(3&u~pr07LXFNlT zR#v~lS@(dX*71-OghflFkh(yn#R8FNO)%GnWCE-(<9U=A%Qni$r_2_2F*_cX89Unc}C_srC?>yV!$p>x@s zx>k8V$d{o8USn{3dgM3!@{X5sq#l3 zY-ZHCodq^E2%DB6WDJ{z0p8;f0Cfa%me|8h7{Cnz66+FpFx_ooASqj&(sx@ATA(l6pMUqjAl;~)LD>oC1kbE z6Q-J?kxK0mQh)~xAFdLci}e7s?Gd~+D0X|s%_MBU*;%A-tbrir#B7AlQi881=9d&e z(wWO_q$M8lK|{xPhl5eFTH10%n6Sw3*-uGVa9C7!3I)l5ug>s7 z1jIT8j5;YhSxkMI07hu?wPO4nLr6O1xhNywt5XGDP;{TD=!OYL(Y>7^;WdYpa|XXS z00Jdij*bkQS-=5-o|Du;4VaUf0@jLnmH>vFkal*=zs;5?TwpFKh({8G9yalQ7*bk6 zM##QKft3fO14Kl(orU3$Ndds&rFZS{(C88CgAEli!@)yA=*^`KnDb-IH$# zS|8R0iGU$BejG7NfHXbahgufE1Ry$KDxWyydH6Z2H~2>38P&wMEdUE7Ha%xo zT<>d7-3;?QA-oW;&S02qt=j;ZJhlUskte1)_5&L^>Dn+H-U6Rc_N&?F97r2Xw`pE| z$7e1u zPD(pP<_yf)Io6n$$sP>MGOVxi@k{Gvx`VyNE^>`jolpfl9zP0v`e6t+0Lm{ZqImz) z3^w+uD)*C0i{-$kL1;6rbTX|}V&WVV$a|KB$MXT$5J`=0^Te!UF4U3n156cY`0+q= z!A_8081btNX=Y$NG9|^%LxsxPR0zPHJqXa#4bxHbz2s@yykN5Xa+iJfr0@^hf{IoF zpPi7m76t7MS~4ZU%j4*b!Ju<3_Y*~bL7AJA7xrTQxGJteeS)&{;t z@)OqT;q$KHMWvKioi8ftGf23#a+kN}5*0#=`=$-scz|YL?H)4qr2%tqoJPS#cn^aP ztrA^Uf;4bQq7-ZFb@*dpql&bSI4rj4>nhDUe=a#wcm(#z1QL6sYyp8hFkO0R5-)lF zos9$Y=4D4RUjg`}l(UP$_ZNNm0Ui~;6}v}R#q>QI%8;23IqZ2Dq#3QI-!SDxzMA2F zX8?Ezqwz4uFppq9XLOpvZ^p7&r(t^EV*tgk!k)2Da^+O~A5ZU7rqZ zA7KZ)Kf??6%;f4S1Cv#3BD`jhJx%+khl04U{mNuE*8$gF(Gb?n5q0u0pl1=E->=o~ z{goJ>;|zn^T+>>&$V&J31S-R#1{_lGTNRJbE7lF6ER)o2le4f#rb(j>b6){ihTO2| z83v@cUYhh9);`uRNXt&^9+PbNEhy!J#itd(pHg8C(=?g0_u1pvAKwC>09vITnE21P zO%Aq92Bpv$cT$Ssafxf|y`CM$h?;g-stWRuZ4BF;mOS({#pnb<0EECMl4_?DF2emC zp0<0eJ2C?>He~MKHG(VScn*FB;IqGl{F>pG29qV4mm@&W6Eg`DonS?nwbP-dT}mRry=(ZQtugGu|4X&B!ZK_B0DM+sGp4=p$hcr#Mre4ZkeaIoo{ivZ zo!P?{lq6S{_QoTc9M!=m>~#)m3e~`F*oHA`Zq>gttcejCRa^hk%;t{)KGO{42*qL@ zAPMb0+yE01HLy$H(XX|4j&d;cOUiC~2r9d-xWKvr%tkOd^J}1>MDvvmfEP%_2sSl;@)T63P z0}PH^g%jS_U2{oYdVWrwSa#UDw8)Uuhm5(e11LroT_EC@+8skaVYL4@XgPlXaV49c?Y@g z$#S5hH7xbfmK>W)9`N$-mZkuDk`D7Y(0oU4jY{6UuxJ*vP%mdOP zAI(INprU@^{Os5`;0H`N${s%J`-HF|Ou&jI&f*$?VLmkvD~*BUBvvy6fb0I-B4BCJ z4!Z3S;OUMzevP`0;qy7P6p>3VxYwDy^PylFY03kCb>odE5y)JFBA13O+NmS>xED| zK+7bGMVy{k9`2(~)__!qK7b|3q(>GXkzoIb&=f#WhVp{|pCN#^);R&5iI-q1gg)Yh zc*tPyumtEzjaq+i2%*I8w$jhxmegxoidX3@38lg0^BPQl+ zpol{$OF!5;hctyRa4~UA|BI>%=53lYq(DL0WQgblJJAZ@dm00%8vuyxC7(e6Z4+y< zCjOfR6pLpRwJFZu(hYn2;H>c@vks20+H8a+HmGHW3L`x=+e@}Dju_H05F+UH06_Tg ztY*%uc>AI{qj6=jf=52h@*Haxf=Yzf=&YvfQV8IiO~UtHotm9(hNM^})aYUA+<$Gz zLXnwO@*q~Jona#m4sh5kYZ+UUJ`@PxLDaz!hBD2(tXHE#e3rzN1Y*?z0R6hPtbHOy{ZuxoLK@`+l0obuU zs9VHx7a2h+h1NCx#ZoF%k^EmV#ICZg)Ej<#eC!?gB=C7F+H@GnIfl!U4nJ6+x#W;i z;i!~d56wC02pFc*lyA}Gzvci>7LX#iS+FmWAVtRHZv$(SH3E1l5KHpau$WPw06w$V zn4OQ^hsE(7>wDOTgo5<8%vdkd35v~Xk}!b%7fe@QnLEA0WYJicbE!#|iKmW= zX{K*3ZgLl9ylR&m0Gkeh3RK>=IOyI0Hb}+}%iTf{v~&C|Hf>56T5Ar)lu18D4*!fI zA3*91X}5~}GY&QZrC;^I^EvdK0NZ+I`%C=*u(F&;s{F_*`v`m)28dauSWu9{9L4$P z0X~t3A;%75L|uo4r_Zo-@RF(b838a2vrmzEfOOf&_UK9pTYkHr2p~EXV%kriTL`gj6wI}Dt}-9-9sbiAOI$H3Nvc^)NC># z-)Eu^Kbpl=3#c0)RaDXotsz((Q~fs_PB0^3akR1k(7R7zN3EMY37YxXBU6h1KkNfm ztaPCZeD>5CI%IxgJu{aaNEQD$jL#ncKL3Y<*<*%0Wi!lj){wFSd>&GNaLJq4Aik$G zUz5`eAwutfPqzg=n}D7-EDAB~2A02rUH-$US$v2G5QkHc!>Nb&ubezJ`;r0zLLHM@ zP7jGE7DX@~gH0qeKHmQT%ck!##8`vnvdH`BzfvR9xn|O5IeX1)o!*+{RiAOiKMs5j zQ|!C0PwaKyDjt|bG6zddOtlJAb>fWr#*GtO1uy=P6XSdifVv9CndJFA0(IGAoB%H` zcar8Q^6a1Shde#+G?z}UH#Qf5(*?k(dE<+AtQj8g-aG~@67Vf1tQ&dREuZPp$tgAf zd>{7L9X`{KKX@bb|Js*S$?EIfU+jMU_`mzYf66@)guN2G^K6adRgwWy2{~z*`>sODZJlDx!TaZ73HV%{4Vx!( zVY4*MWR&L81-sjgH{b=b*fS>mXD!Y3@igYS)O>JxFhyZll`FF-o^Go&~wj9n)XbAy}rObCGM@5pI`D3jKu93IJQhE&MZZxnA5=<&O+>2 zuJA!iAmctl4g_oH5XRTudE4yK8CKlGbY`$#A~Xg=u*HzpKAa1gN4!TN04xEYN_PLX zaHAU6sHAC~)j2QtqjzV9P7oa+IxXFR7%aCRSu-r9#Rr|UcX~R&{g*a=_9v*?e3tS` zWg(SJPeGyq8GWu)`@5X10zO^fN3jUTm2&n9$tz<`jQd`EY%$`{FMJ ze0q`C@maL-JXhGo2Is^k7808~gupSFX)(+rs*Tz$XeMCMPAoql*kc(L19CqsZw48b z1^Hag?OE}RdIkU_bII9F!wrM?{2K3L2gMeiiakNtqO)9k50Uvk!dDIZiV$pUg+scr zS1G<`*!I&alYP2HSQ#G1d?HD%CmqQxdtG(!toQab?QJ~l7N=j>zo(kU{)VrdSUvTxa-( zEdz~a`X`uRzaUG`1AM}0?*9P>*x&G4?}M4l9uweRtp5Y>c{>afX61VmUNz=I-mBEdA))5q3bm&&s~DJ#(91LpTqbqkFdhrbqyqS<%6F9Tm*c=s6VHc;#fMv zbNhX;rThIp_EkP~4)!NE{Y7MiaJpF-`_wa!S^g$ND_heb#b)TV$r);|QHFordgjIf zJpmbWx1Nd5EZMVH^~{7CeE{CPy+-kf;|$p^^_qj?AgoUYa0DpndbHt2*bRiJM{I7EfN?sZRYV2eYgdwGPrP8Q5|IdPA&Y5yn<8wRcqK4Gzq@+nNI?rFyZ`lF@!tTNg3I< zP|vKxzCTD&&(?y3s-R-Q-Yh`E- z6ktnp3bMf{HZcHD-vKa8!1Q-hzqT(=DbRSXebqv;E1^cUml~j_W0LpRgX2@Xfv72P0h>2tERsE{~2)dSjcREDOttQ`onsJm>i>fwv5Fk*?Ash-`(W z5ddbK_oR8^{3Y@wOvPVsVo=w@cvPdtp%z{KP6lT!PfYb}|$61AB1k-!> zB|iJFG=xAd&-Gd6v#bZmEP$XE#S=OPGkI(%YArVA1$_Eo8S^!BK`e zLC)Z-({;{6SsKB%B!Wx;xGMabWn!TxX#E3J7m&p>949`w;X_KP7dKL${$ft9E5 zr={*LfKO~fob>VDn1Ii5bDvs6^~N%_tgP$Yx+Xc-PU+EqVZZ;G8h8h`ZuM7eMk=o!j)h)UuU%WVe&1M5YvqwS} z(w-y8bVtzpiw#lBJ;Dzb+!+sH43((Vrj9r|B+XjH3QW04#08nu}MM8j| z!2i}+&`SV5FQIp5kuIIDGo9zg&mw(Kv0Y7fszrq8?lS{?IFlh?YL)ec8e2xc%)kt0)yZ6MzVLA!& zxx9*I*+}a-0iS;dcv~kWxWM(&z^~PXmjzyA_d9aO@fAEGVLI4i9ce)f23Yx!)Z7Hm zcl^c{89Vq-s#s4_pmZOa9LvY42e6@s@TVRaj6SASVtqO5SUM0=P-KcyV2V=XpnCg& z5#=00=1u{q6y5kR{wln87Br?=(S*OPApzH20^oCNBVk^{UOK{;rG&hw8k#aUr2mbN zN`}{qHwdyHHgz}8>ta&oWNnoD%&Uu#IlQC*XQHip@iF(}gK-jE@fp)J=?2s5Gidai zd!)wg!X`*!X3h_3_dd+v{ZRguI2U5Y0Y2%!%zVTFD-u6FBygn!)91sNcu}@YHuBIE zB7ZZt5rEtX%xNZg^T?3>^+Vo!-v9K|JK%G|R4nlM{{dnN_$-+buYgaacW1YF?C_bM z;Im7p&lHEi6bFxhPYVvxfUD?ux_fJWdgCxZ@3_qkz-Py=aR_->jOUZwn4lThdqn{9 z9S+?crvFC=DOg|B)U2w@V_Hbg-J?quG!KnVVk{jr4`I(Pa(D-7hcHy1ni|)P65cmC z^f%dSjw?)7+1ozG_#EavabA+d$HcKw*rwRZnV0*7s~ldLBPQ~VK71)T;C%f1aTtPY zc5j4@*RNs$(CzNEy#QGF`SmgD#GGacE9zc0-1oPDc5eQ?kMCm}bKaltSo%ymY*+!G z$RD{sZ0azF<#vMW=-=tV>kJ|hwEk3ppYe$Wyp{X}r%=oReumIb2LK~`pb`2A3TPpt zmBDg0u#alpA>5s z{M}SLVlo63imVtA5~;{bqdH^yooq$jB_}SybqXEfCSC(HO;rA zr~A)464X4^gnBn|Z?8Z5_N{NIJ&7?ct8ZYia}KUPHWTvhZK$fbl(wsjqA*!ZdeIu7}*-zByjsLShX_mT#Igo_$%o z@NPpRX6zpS2G6dAhYS$dZ$q%s!mQhT+9Wp!I1ob#W?=@U1%e}!<1N18y6oPH{LvSE z{H1_Tg5_?nsN3+RY*kJ>O$hb@>pN+HPr?)Ud{w`RjWf1_XX^4}e5F=lej%C0n_0}i zP`dzIh4b|AsmULcoSPy!_xy(QlxE{nH&)axKfves9J1oa0-2}-IosX=pX}$GyCwy& zk^O}v(o)$RvpJsdxd_xyE%j#~w=~5r*1P>R%>LSM)+UiJvOoVhk57QlCSf`NpYq`D z0-r}_+5(@db!J0#&P*D=Ggvu)2lxd1yvj4M5b*i`kX--Ihy^|a)CV2lGlSGUfz+Mm z{^cRJ)0_!%--jMN2KXEyFrsCFXVt<*Y+LdgQtocT^vatAhoNfVqL#&N#!5F%sne z5%_FyUE(3no>)EEF$L<<0t~J*fTV*9KrGmEc_%6~ERQ2Dq!uAu@Xz}|6rux~O?*CBCWHHED7I5G?Hxn`~>AF@Y; zAxrqpV_17*K1d%Ii4I|c!NB_dxp}@L+n+pk3_9a99Eoyv!~mf?gOT}%FdOMN!m!H4 zEZ04e=D9Mz@O-bf861}kIW^|^h8~gO)h8cFlik$f0@L~d4F!BY>^#9V!{Ae!H zZ%{@R@!KUN3=&5oE0$O1;l`3o3FsO^*n4qm8m4g<_~f-@NiPTZEFZog^B`}B5W5s6 zNos)K%D(PC*H`}v(Zqo-qCCul^qpZyQVRflkNK=t2i%`NFx_gbA;i}Yx+GoNr8$0_9dyUwoSI5|{X)h#q0=tTxV%Y4I zrp(Fptm&ohT75+5CisI`_kr7r>0xOgUpS;*d$Dbf)_16J(O0y8#L)DOayh1x45s_1y7U*rue&>l&ZqS|4HnE!zRD3seUCM`SucZPq%i z5=u+o?1MDai`|JV)fNW)W(8x=FvAb^J_;Xf5-xk)GiQVBta2 zpMbSDMNt@sm0>Cb4avYZBa`$pz-JpEs|V)hFon!LGrPtnvw9!7T#sdN#5jB{0CI#U z)ZPxw&<;K-btvM1h}W=OT0Kk=HMNXU_{Gf>a^TtJQ8T+Tid@%idhw`{Jg}GIV||~% zk%KGqDgk91qfPnC40ABXxalNycfovcIV5#w(h|Y1iY2ML51G3UlVNuo!1MA7Gh~XT zJglJ$(z!T`nNb0GO6o59vcS(eDTR0hLhY8^y+n~(qyb|%Z>8>ES}OUQui{SupS&k? z6q0|Q@hRZ5W3V5lfT|t=0FPsJoZ&2W|{1!fMZ#bNG0X~sR>?4;wxWJ1C z*<<&iuocO?6#S$BC@(;_8!t3(OP+YhumQIfY;y&ROv&DFAEe?rhd%*)LaFz0&Dj)l zCO-Sf3O8TDU|>*i#B@rg@^$frgEJpZv{2pQlA{%f#A zV9>2{A3g@CUt=05x!ny6hbA)q6mpksCVFpi*BbCsOymzrpzYmO?k9my?U5Xi z0zNe%w2b<@@9}Bi^90~i(9OTo18=|(jlvk#6|jf~MrYUuNPT24jq^1`5fg-29hB@H z@Ka$O#7-i^1tdUzRLJ6q%QXHxlY{k%92OtTOocEN-=JwyWVm?mB6xh>r@+wruOdSd zun&MwO1}&t3^f8*LZqeATUqvufX^%T`gA}fuZFmW|-P-c;Nv+&j2a}17iufvje6-GR=#VU+}8lGe^jz-((1lg4CN~ znA8D22k`S9`bzwvwdP|_F@|GE3zi`2oBFkU+KZ{D7%8Guju7QbR09%XFpsv7nd z;8T0`Td?H|0H1AwXRIF@{e}F{?o9wlaDjb~YQlLXLWOD?!a_0xH5`|1J+pH{@C{d& zR-1d+C3~B@dZr(cc=oQInZ-)Sha9|e{1cc4z$eneZIm+GnV31fAnfe};FFYW_LvZ| zS3eE#Wn$OXrTGm3pGb${E~qBW6Jpsjf&CT@e(XKqQ^7$o7ruLi7xXePJ^>bX0lYRF z^JcesAN)cROF&(Er|((*M#lC{4(>s*E?;ufDBSTm_uvg+p8BRXpl%Rrm?6j_ws)Gl zZ^qR(0c1$B?yDt!H{){*rna}5=+rZSAgQwywG8zPFYq8uOKwkXW>UcB{nzMXHpz#|tqc(%m( zhJuJ|JwqtVCm&xN_$15)=B@gekO%d_)+VGDe#Uiq$YafLeQOS}U^=Q3?s5kR2{7-} zmjIuD<&&IK7Wkx>c?Wz#;yAha5coui7o8B}6X0`|A;eUN@hSFo$k7EpJ41*R!GSy2 zU4Si{B;fnM2k4ibjQS__cirB}h0nVGZl4K`E#B=jB7$(A){e0&_=&}^&lAxPo*QB!47G zK86pgi(A6|T7)5KHRwlaM*&JH$p|PKm_?npi0W|>r7Y8m$vy{r2ItfbM1nMfndA$U zlCRA!=h5p_6Nd4P$y~wsbdS;>GPowBOyOiJ560{o#ZNj2h#cfP^EFB)@iPEDL*yhi z@EN#AhUd*Nq`deQ;8WIT&Ejj;mbu6t^4XmeG<88(?5N%kfuQmL0ssn#F)WyT47&Uf8x(;1=NX^B7x+A}WR`Eh=O=gZFa94s z4txRvIYw9Pl(UzcUd_z?Q9KLqM_2_!RNAUSJnBUz7snjl7~ei;?Vqw)ad^`q!5W}( z8=dV;Nn!8}!!ncigZw&&Js7K9ybpYee(ho750R)gYQ1~rA1*R=gl#(DH3LX3B1kQq z1f1lLPVoA}!-rKMd9jF>NfGc;o*cK1ex94ruOnbz^w-7AXB3%m%*IpMQK6 z;8P~@*Axr@e)|VZayOV9<4uFN$}uT}M|_6IOuTwzlB~%UMZ++_=d^%N3QRE$KL9>Y zVF{nOSxni^;uLBNc2YgNI!}?IbY~S%*G%?cTQH%{K9|=UlVdzt6EHRtEv7>9C+I#X zro~=z!t!-Vh9Nc+zBn#<<3J^s3AheKEq?j#@oT^*G9hm*g@pImh9K-7;@9{F*$?K% z$4G_h00}pwBHwuM?0{h!g#D)F!%xp4`Z`{+4!q4S2bgw6=I|>)8a4zQlGpbH&%QMb zy&I(NY3W`};}>&~uA6=2OJ{|ScZhz}Hiw5ZG-OcWK&BmlK|n~5gDG%tbO=8~7~JevGfXW5Exa;| z+k`Ugm@Lw;7trEf>{rYo1-K(hdhJJnPxdjaIIdxeUT0sKBL+GfM@;08@lm_z1^C39 zQ`0?w%Ly2t7Wf2wdkcJ4-vd5heT4DJ{`lVleERyrrhj+rlYr;FKr8B~;XebV|=I|hgCt20xT~Sj7^lbmf z0iR-fXxiUl--Kc#G=;El9^Erj3u9)GJZQO?C+c`QU-)H9i|u z*?dlboz@Bj2CW}81YCZ=PX-n4h=~#;GtSRC$=N$HhMytluFhO9tnC~H|IUj&0y6fH z+7fhutW&E&a%yM_St0>NV`A)MH!yrB>CHafoJS>FB$kvj+qFoyMadlZWDkl zuxC^Q55R=V95Ccm4ZN2yx%0BEe^m#UZUqRjcW^H{U11X$#PWxGabjTp&4Mb;eUe^2d*>+|o_G=Owi zrfi*hSTVd8M@?|(ea5GNPePHXOMS4KF7~irpf1fl6O$H&4kg$zHDWOkf)w=;;FEqP z4)ED3c7V^SfX{2Md3Cd34sO>VZOxcjn2?X@oAlaz*CdwHHwoBHi}7jurYybFIW=YH z-qY&~(CEkS$=jLx4|(m>UEib>V}I?B0G~Bvy?Ehef1#dXfOH144XNwqQIlXRSYv>6 z92RyB;7>OR%Xk{L@%YS)85DDieOP{djWmo{sSLIOB#R6o`{zt9_MIsY{tY`ZF}6IU z7<)|2K_YZPUhppn?b^0U(3{jxn~A2$lyr^ixu{8T-v{{2lsjxLR9dO(J60s$=)=cX0X}8f1v8a({_-{FaOavyF`PewI;~#j zgkEN#3w+Ygbb(KSYEg7=cM;`(6gy;ssVOZ>6_a0Prb0HXBntI;y(O=`%!(F`X=o& z1w3yLsVmht9i~xm(%^OX8V77j=Uw0A78ZNh>$&Z4 znP1b7O48hJB4=i)9~J*by{MOFDzgrgtCEZ2z;=S6H7^pC4g=(egg>nja15!YpRhRDT=bBfp2U{sRJIcUpQq$q z;1q}L1ega>_fIiC2i+wIyU!Rxzc7_7Hf4a;>yXVh4(Txg=P{6nX^O%PO9wx%vlkh6 zkbycnTdV|;kGH%GP{ZIslURWg9J~mgWNYA>iE!^Z%MfBVAhf^?KYxjjJ2kLa;ky5d zju6&|(H(@n5=Iqq5BFdHNJ7g)iy|6cJVir z?Cx<9#1fC2(K#ggtRcMo`YDk5xZ!$8My;kYCijMPZY>hlME#bEL*QrA@fMabEtn?P z_(RrUh1Qk03}Z+eoIpw8^0Hj7Fb?38FvG5l7q*$mj!8)@nVV}KbcFyo6LLwK7Ro+r%n zX@fia`=))q>^hq^rqCyMvbuAg?@vE|4fw?C$V0I!My8mV+g!665Ioz3-2w2~;#&AE z4~OnWrlP(0p~R4{t=M}pVAd$~YaHIS!al(XT!(oMD9kUDg|;=3?|$z#;NWd=z_cUd zsEj6HF~>Q;NkU{&Isw4ozTAfWCHA!#pG|Y-TQO-x-m^oN(re#7U`VtH<7gw7g`RGcsAm9%XR)mq zhtV3ig>g%;m7{z>0fi+8@BOpAf;rR;8=d|r@Hyy#xnA0^ERJgCl}Rwru~MXC-dpSj z8odCYfNqjCSm5)HaX5{%-Uskv8K1~7UTjI~-kI)wAa!Sl`R@Qesco=kP$L(_x2zXm zwg^Dy06?ce&rmN(uEAdXix@Jo0(gE}K#lMrCj*OHn6qfjO(mByds`=@yW(X@@svis zUJfsq`hZOF-}m_JGCoP61!%J#pRJHdvE=dDM{yh{TwoBN>9!dAIi)Ex;ip+~5I*3R z$0u_4fAsh~v>u8dAB?7o(v zAWajFk;FS12N!cfz{@pWZN^NnCJE+~XB(0$d<9p27_p}HU3qQoAArvsz$eBYoMQkW z3_q=x5XE=LSBRk?jfMkj7@yB)0=TLNIWq$SJ{=kslF}+xZIs|Nox4-WbB99!pJo0F zvLY-i@`uQ3pCg&VHsJIG4?liFttza6Ago}eNDtENkB*KIRA6ZMc_*A0w*x5HK$XM> z-pDPpRh=?B)p=86l0P#`8a&hdN2~~b#U5B9KG^l8%$eE5uj@H7Qu&HjN#0i8gLd5C zw(Ig2Kf~j%1$$HOWufS)#R-Npt15sQ5+Iw*nk4f&u zKHZDh=oF8sfhAw$UQ8xZ@9sr_&;C2$^H)7S5Apa+{V|VE3Vh?Io{=1R07AsVFbWoG zn!jOBqX3RhO#>i-tTS1>vGokjM(;g1KLI0eNzzq4o zeA)r|anu|a{t@^@B1)a8y3}_~faUhSNn^i|`59JZh8pdzPAv7p5CixWkkw&)5*h^{ z_?Gc0;M01a11!|2NzI~3n1>*+>`#Lz<#=sB@Y+1pAjh^wtK4+MtJh zU-hq^E5_hKgb@w@r(s+N>c1cO9QBiye)WcWrc>jkkwtgBLB%kHH1b}`YGh>6vVFz) z9h)KK3POm0Pq7SJ=U5^gFz_J3zDVkQHaKhY1AoJB@qh3HBou|8Q+0aSqyaqzvi=OS zanHOtsZu|&N1*D<0$g?<@{gXpkAEd>#!14oMn(tUO1UFk>!rgVt(3cXhz}+KK-N-& zf%REM!09I*Uj_KA+df9uPVU*8PA`M+X)2X9C)D(^>_~DBcL5;<^^QRvn&G|CH8a&^ zd~(fPq_J=ji38yC^*7-24)~< z49(tCznD@E2KQCWyu#b}w6$dV!T_K2G1h`<5XBv22p6?%1Fv%h(8}ZS7gpk|v$0XS z{ozLdRn;*2gzOM>-wsCu+($Rop2S~Eo6F;8+()u9+cF2a+MPX#lW-%vecK(|37y+r z+X>a2BkWh|pLLA~k?5<3POE3f>;CMVvr2m-{d4hGfX_HKAlLFEW*;c0@CU`-Xp`%F zlYJmvdbbb!^9%0|P#M;RhRxIx!z#`~`7#e8Q`ev>B=Q{Lce&?Scd%#Yp1sCmpl;+9 zuKTZqwN;zI!A>1KVU`&Ds|LPL4LmwRpTXc?b+DNJf1dIA8$CX`24vl3JwB;PzcD`l z`IG-zfAaN!PuP1MDe3P8J~4;I=s4`G8CT;HxljfFH{|0;AF6n>N%AUDxjb?hAFyDK z@wJ~uHcY~pRv(S7k_mfm%cKT!p}$N3ejb{GjboE5z+ixFcg3ptknQ0gqUCmnVXq8x zAX5k|k~&6ep@7eRHi8~x$XA4|WMRwXpsQz@pfWfmZB7z)fw0=f1b*HUq}MVH{2`l| z*fvRAZr$={cmMUzY<7H$&-eI`1wH|(0X|bdv1zli8!$eh>1&FA!n>DagZ$Hve+7J2 z0X_lU-T|KgZ#108+0OxdlGu(9N&|4Pfrnn@vTDvR+jt=&ov6OMy4n<&Z9v6aSQ71C zyfZ1}N0CpA^&uO8J!UF@Mc@`0sdo-qDK1flsz_mvkXA$$G|7ZVBX>74D*sm95BKDUJ6e@>3&j;JzfiV`uXaI@#zCZRxp*5ifQj;Tid85o$-k#$3z|kl{8Xegzf5 zRyuuYgAI>MH)iu{0`POjTzkgNH0Sa|SQSt1!QKMCaaIzf$Rl5af$?frQf|NnlpWU! zA>^{xdbcxv`TO7-@cGy9`26Ei_rC)083Jg*Q}C`1F6F3yVZap#eY=F^Qm2j)Y%SU2 z+ZvdAu?Nrc?!6d_1ns@3I@qlt#L>rr&nx!iUZn1$O9YD&FltlXVH!$i{XwexQODzR zJBMF-_V=Z_{~GYgGzzy9)iX5g0J}g$zn2%bo*_$vn&#bIE2(?i%b8Ct(+KAYL5-e0FP+chCBpUftWj^j8KxwGS(lxRl1bI#~hq z>S+bKJdvD zH{??CB8@Q8-^NwVTwmd-c~P?_#9~f%m>dGJI&uxkDEt9G01u5d>>{6sG(=2#Ju>RW zuy-HduLCW>&kR6nndw~F<1|+aYA_L+ILY<~EW2)Yb=u6_pEeV-m>l0tKzf>NzR316 zgeGKRuw;`2anMekQIJ`-jBTy7jgjyU(AiSc1)JuEX`s^d{_IA_H~zej|485yupFRT zlFro68%XQl13u9R93w5r@k#&b$G-$V6+H9~_|)P3oC*Ku06rh$e|u9$_Ki1UtA1%} z4H(c&0kjC3efJP6Qk%05ah36=hCnr zdz~IL+1oJQ%VX2bZkcB84L(k7^WtjMysA!^8tL_uBN6lHjwRmLf*mefi{cIv*Q9xU zSVYE+zZ}*UHPTOYB6rv4`Mm!1clic=AcuomfoW$)um^ypq>_^*MYY$ra(_!RIdKea);;^Y@MGLDaK zAM9)9kZ~B*?ZRs$lcZ6fbW+{lq_H``@a?qd{O!Jizry%*;c0$Dm}HBZre4Qa5;PY_ zE6)56@3pqhd~yVC8G2ek&J^(43Fl0+_dnfHNAr^BM*_8(lfzVoeKvi2=26WGz=TfK;%sC-rx_U0&~%p%qODOPvSSbH$Ns~4@@xF+w?fuk^~;oz~17O*==GvK%zjbE|7&(5*oG>z{f#@<{*s7LG$GR z1Y_g^=!t9*Z=*wk7E*XV%5Pgt%?j+XuFTNoj*?J2j=vg``-d}ltPR`uO8j6o!|Wh{`cP+_>AH4iJViP6Lk0m zD|Od3BjB@UeLUpbq$JAh<__tQMSOVV0|Mg{@bgbU{w45v(g8j>z?I4^TZlgdd}0FL zi!>~T40Hv##&_J04+l9Q+S~{7e&MV2ec@*4`oDbm+F(Q@UsWnIhJ40t?+;!-?B#k% zz^774NvM?6-M?Zo{vS;9D@)xSzFzZSCVVG#4}nnuT(&z)1B4s-+T0Bf^xEFtN4yUE zx&*>>?tPbhin;IGutz$;b7yjFrJYFK5As9_LD{ckQHk@HoyIG;_1CM)7$RVA+G5GtYE&2Op*F zR|z~NbEbsPBV=fE#VyeA4-$$s1^p8_IDuFTbC{nm4B#;v#S#TWcS5zaiU6PMcty&O z={c*eK^7NYS2nHd@bf+12R>mi-bvjTI16Wxy2rv!D?gH5NIjY4{J|p`se5LxBXvLK z2a)`!BXytTy`Mnp&TCsk;`4n+<`agk@=2-tO^?Z=bSStZ_}31RB#(#Y0s(XLlV$)v zPpznYJpqp2?ws})`XAm@d+#arUGjLAgL@1gB?o723*a+@7wN}<&oUmzH`s8v2|Mu* z5^e|ZxdPhqm^D9uyt9$wfZhYpJRrUL);zr&0{9#;*LYKn$NSBr@m}*CD*ywdm0L%W ziS#LTinm;aATz?u2qmlI>-=uN_rXzKqGcly@QHC;M!Yx8B>R^U>ZO%c}zqSr$ zh^sQDQv>U2)VUA8!CYvxyUXFCJ0{OX@DcC6^uQCqPnp^HZ~T z>h^Iydk0c$QpG)p>Itp6O5z88s!-CR**a^OtrYU!0>CFM`rq`hz$Z*vu2D(d=aIU1 z<=|d^&NlUZr=C%*uadvxXZPXvX20RJad@!sz~d|Y1f2T}SHvy_L+4BXu7Rd*8}tTljSr?OsKg%Rflnhb47qk5C9{ z1pQkr_cQPRFUae6&6{MkV~6op>fU*-uGD=3srv+BYNPBkLw6G0i1Xr3R*%mo65S+H z_Z_=8B6Yv`ZU^~D7gwUB5gFsmiln-9MOLgZa{`U_z65}&>cfQ>b7Gw#rQ}}Ga zXOK&%+c&8{zp>d3d}Y7+uugcl&&YqX(>JNwY12B%c7lB-i@H2;MP@)Lphp>ZAXt)IEmO zy_%Ng2(|ki<5RXcuYWV}i9O3(sr#I@eTpmiil0e1jLD}vQg>=#N!^pz_}ug1sRk}N z?xgP2z>>RLsk>_6zae!W{>)N$0iOfo#yhf108_`edU>qZJf+g`aS#q**ow5wytB_4|?weKmzPtq^j+E-{<}Rzr4gvofJC>lIXqn z_ni$=mK@8G6U)xc4U#cjC<14neP++BS+j;#vispi8gvuEqG)^~?7fOstXdlN$6Y0^m+(*oo zyoT58K>hhLPA8O%sU%y_=9~ib+#Fxa?GYMjC)Y-#TPMBhSf;iCK4(tA=0K|q;8XQ3 z6jWQ?{t5Wh_UZNsz$dyFst1%~a^w}k8ME%qq_YnsbAJ9+{x$#ke$id$@(u4oH;AkWmI41-R$M*RPp={EF$20H4YN>x|`$>5KVUZ`eO~PyQb8 zsR^pKx|3{v41B`0)Km5|UY5lNOcqo&)GjP!KmiI6Uz5mcLTeeZx5s@FA(DF-5^aFX zHnIxc{!fL0U;Bv3s6$(s>T11ZsKBv+D@ZN)M4F**7#8$^fhu2is{3EFcKOSVOP*&* z*qQ47XoKNKn_M(W$aIQP%K0x|7K>O4x{Hy+Z_0^Qk%`hJiI_F`4&pqscEbh>1nFyg#Bn-J>{_ChL z^~=8)oyT$p;1hpY`9TGha2@I%0$^=PKO)4Fa;eQ zc9Nk3!W48EaMEV7^VGfq`D=yW!BqGC#B8_`I!E`zjkI{DQ{6utZd9;_q=Zgx%{R{w z5Y}@8&~!5`n;R)a?gub%@u$37#v|!csWxVql!(#8`kcb}bi%my__f;ta{)-0PK%n1 z8BF*o#%G-Ajb_N~Xf?U7u%+czeV31%$ z(ggW?NtZl=A?j?OOOF9U+Zz(@w!FEawd#5(e^z|?_khnq{w+z#4EVe;nu$LF zpOg5Ocvl{>#UIK%`m_6Sw>+iTwc=6DzJCPx)Zs=KoJZ$oxRD0nDg*Gh!;L(%;YLh# zPXfH809|#k(HaAe;&zI@w>mh6Wad^iJ{t^<0sMMD+(^Rf-?xHEp zV92*elKb{Gi3>v$7!IlNlXhX0c{YEKR6sS9ZN|TD8~^&R4}9uyBL;;2z%=m}48mZz zQIz;M_G}B_6a89%PX#?AP7-cnUyXr>U0Cesqig)DaqvJ7#817nKuYY(gDB%(gG3a` zkRysG$2vR&|H>1r!;QMc?aui%32i))@WxZL?Exmy%HEFJWQn4Zt-&C~><>sUzb1(T zK)VJ&yB^6En%NAu|GZaXRb1&k@j|XJ8lS{#&v5krjW#z*`}h|MmLq%|T^<_GwD8+) zIz9Bf`+H~JlvrVi&Tp^s274zB^!qOIKD5DOQfx0UWbeb_M!&zt=WMvq_H4Kj8lUS) z^vW5g@?Y$K7rKHIp1=3QjgAXQam=&}+5TMHEO+t(e8ze9Ozxz^jhO1LLt+2<;YQ!1 zz4;Q5?1SM($P~1DT5wY^(IL?V3SEz$%8D6ov?@uoOcS2r-EgC0h7J8R+$a~=kV5=x zY3Ohx2C8f^h<2AVYR*C%F_Z3lMSL;f+}q1GQ{;8H(cq5hrRTm55OwoePZ)0WL{|16 zq0Ju^9c-k7ltOe}l+9-`+~@?2Poy1A8Gn7+_*WQi^e42s18AjIR*i1sJb>0c@L9jO zo9YgjeWbDzkjt*?|I??s{~Y)PD5N-`g}ozmB0rn)sbCa7#!LPbQH@WA8>yesXVGV6 z2oibI6dMl6D~zAP-=H6QA78UcKlz$M$QThH_Np^K)2ijs_g!GB_@)#YZWiVlenJub zDH1%}X1EbCB?b9OvnV&njJ$q(CU4%HN(LWFI?8ZnVp2X>2NwO-m*<1wMyUrn+{n*x zqp76*6Y&oU(z(^)M&C<=;YO$T!;R49E2ze&CC+f81f5Vxh8h77K4rMkqh-!~9d6Vj z-fJ83P=$V9flrkyoW;OCroJ!lY~01ckVNn?X22&guod=f zS%(`zYAU^WEgAAd=C#Sihr^9b9K6iLdWL&Kj!@I#Ms6^i-)*=NQdzdxlDODwKDw4- z>1Ekr{hpGeZ-!KtnXb?3p^O=@dws)zV>*!3&axfy^xr>=_^tcrf8)>pGVrPFsrg+3 zpk47U?3m$3525aUgUI@Rf)*2|R7;a2u!cs(6~O1AS|0J*8cqY@aq1y9+8RS?h7;`) zJadRIq!(q01AI9^@a!Z2YkWA|NConBLU$106H)ioO{UrIn$4b^^8sLujk%SbR>@Ok zxs^6kTWdJ0>Hu_qXk}VDfWRl6#+XTO>g0Ony^^a?p{X(e0ga?i&*&p{d5HUlwela} zGf8XXBT$ELl0Mn+Gov16$r!dxt9&UZmkj99LkNb+O_8e0`a1pB$?3ZHs3G*KA$$|MUnSWZT;uSUsdGEIKNwA5WQt@B>A-rc{`&dPm{{#H#I?tgx& z`?uFN`AdeW?igZ_@`yk|^%@^_-ep`f5D_@QLs` z?fG`thSOF-u4(Oljux>p4v#O;2;xE&7*PAvJ^jVC_?P^bns9O7Nw^$L_Fi~E^0WI; zQky%HN&;&6EwU7V=>+iA$oxfG*RVgX=`zSN;3hxMbjoFhb}@7qP%wkYc`He(D9 z@Wa^jlg2kPoKMJ+sNG!LN)8Q(0&zwGfTbTw6YHVL6W@_AlZCgIX_;OQ%GXTP@eL8d z0A#ar#ualn^5Q5bKOW9ZfzSKE`1fCb1uk?wlsV^-KrAV1j~-Igc__k`xR;3kNmB2zuwqcs>Vf&bUSTRFU?0u%JwammGe%FfiCFzh8*aD!ZgAx3^8k7K1a+S;Pd82j!lq1mc#r6Ssa%1 zPk4Pc@M(s{)JR-M4Dk@jjUpoKPygB@39LS5vU*H*=Ky#CKbKs};JUhF#GK3LGCmh! zN=%5|#*GZ}CK*6a)j-Y33ji4PiyF7hrnQwhM z1Ax*2Gx^-f1Yzj|!*XV~8w}MbX*|=E98x9fcV|XC^Qn*G%pVVGX!jA%bO0vM2`x6l zQfkaedo(XGI?t5Ipo21&R=)u7*>cG1;ijAn9?BHpb0b9u%VJQzfxYhTgEnxk=-HyO zJI~+2{vmJrfrn?znWWF&1oDLs&_wg5c`ZC_K9ofY4w^Tq&Z?{T+lL#aDF!+_7t$+n z-(bV%Zs}sU;J%%6X5=OSpL*u#vx_b|_haqxK4;E8@?H#@<+Jb4obHv*GPC4@lOmxvNM>l_73p#Ep{xg&s1)tDKF2sm)c%u2Ke~_J}IiR z$Jd`cko89p7UqM7te}Ji|CsyslIih$R`C$VCo>%`XhlBnStU=t_sQ44+i;^r_9>v} z@*cycRsf=XI<$sCu`oWzEb^V7>7enML)`N|cN*bb?){7g*}v>+V22JEjM-*j z831U%Q)gc;N@=(XWDjGW80Ikr=SO=9*<7Zh33)uXcE8a1Qp})r{`Q@m9sq=0=cR)G zuHd_y+_M>h>$k1F3+?>a`o1JMuai4Gi*rHzg+*3_Oe-0eq@R0A%g6CEp@$v!@8ZXZGL`ao6LtJb0LrG&ql!EAeUf6aMt#J7k8wKa}eiCk&UZ3ETri z@@g@d@_vX{d>vm!oMM>Y6iohc;8U>#1wTFF3(ZM-YX_h7(8R*Yn49=_KqnYoFX%wm z-$7`4gHL+%TCVCn;zA(~W?x_^?o>hhv7yR zfKLZfO+E_L{*_0Nla^#B;g*L9hV3EMw$a~OUa_{5D75K%_P#yBb1 zaa57bly!y=Y0#l8p7H;3;M2G*g1`iD zQOz2N!ZP{_C;qiW*b9psqF)sFytK$)FYNN&C5N4pR(GZ|KYb800H~7#SIy3IU(FZf z{P+gDHEqkPv3PHv#55G}1=v+atf}k{^`IFW8>0v6O~I)7vw%-PPit&bTrdvf3VZ_e zeg(sAAB4ftD;iVa^A`X5^3{dBdVL|M=XnG8D(I^__ZN512b=Vh_nbfb?#N-;#NT zb|sjwn}mU5sa;9+2=>j<4^m)4^F^sEC-teElJqoe^D>2vV-2j+#t*Qt$uqSZm9KVV z5`r{AC~Xt#aE5%566+y{T6sfa9sqL{i}i8lWdqJ9#d( z)0dLV+#+Yur@%Fl6Z8j>+5S^r_rRw~UX``8h>k6c9mCpTT?PP1wR76W75JpEa|~-o zfzSLnDz^C@39dhu5SC8HWIN`R2QVH|%DhAORJ#a9v`-zv1lMAuw#{m*yJ)LBXSqtF zeAw!~2_1wq z8z%stFJAznz#J^iK#fZrTXAGHQWAfAv+kZa-ahBqQ^uzqHoY@8V|=QF%GboNkC*+# zu1wga$TwxMMTUK%6L!@O@UQP*4DqtyBV6WO-=~PbBHMIW=v3f6TEBzC!h7Jk0@d++ zx-6GM(ZG?U$ZO@y?8|1DbB9Fp^E5u0BL4ao;FB}%#f{hj=z@Sx;ioW2mf{j)&&+8Y zjA`s!J=eD5BPo?AKokFJ9GoTL`_&h*ug1XI;yD_?f;MrmGAsFK7S+VS0H3z_GBQ^W zO(GXdQ8*!idrBg=6`*ypmmz^WAd!1#*efs3oALrc>mm=B`kCyNX>3It>}`!_A^>eW zixmA90X`vCIQk_tok7uQ4XqRl#4_c(m}b@zdLPsL;@M{|9{;SJ9>g+ZY=pLce;5Ol zh0tJ2NMhv+1D{MBf7M%;qu#zuDLieY8N9Yi?z9?4Z@ws2KoVQ~)(l#FaX8DJet3Js z`t$6OJ5721d~tjyc~e^RrfmujCU3IPx;o+6Imd77zz`;@b2iPUvk&F8=#p`8%}j$= zd9md3Pa2&BrTDl7=j!1HA6E`Sr7M-&4eT{SE+};t1RKijIcA zk*LyR^WG`=qvGJ1^%9E*(W+fs0PNtI{Vzxy3dr7*IBLZV1tZwDcYd?d)TrwIMeC|r z5B;7oSwil`faOSOEm9!;Lx&qF@VPgW9!M`!{KN+<@TpP&BkpHI^>(kG1AOwF0Hi4J zd0n+J%ug5PmtM1l`XEgFr62n8|IV)dmElI*CzZSir&nbQ|N4x3wIBI8<5RWym26X{ zTdOI}&z$N$4W|hy@H6C3;ncEhE&_aN5d!T+A9~04ex_DJZ}Cd=Ce{!e+!ePqWcXD- zLvAq{aBf1*7>$Oo?o-m8EXmlm1XCUm@hSFX0qyv(cqc9KP8qxj0i$+(E;om72jn3QsmUjYthE^$%Z9O#-B`W`;vVxn)PDJT%8kS#6FZV+F z`2*7h^O{1{E97Z%3Vb@3z!;Yyoh-@zLyFDVBLLe}3`rx_NWnonvQei$AP-3FcJb;= zvUF(ZV_fE|c8J@f_?y_~GnxC{fX|;|U}dwW$OkkAUcnR(QbxGOz$!)6ym&zj{JkMJ zU6f}r@JXFE{&7S`zBREB2ive62sKZLfu9irr=u1#G*x3@`|kjJTIrZ|Q!ETnDAZUj zK|Zmmu^NS-S$ED6atO+{pLI#-`oE#ld&#p(bLQv2NUCPLL;pIv`@ZHMam5F=Hg%5IDX(%*d;QjGSj+j=|oc zE-b3o7r~jOcOJ>&?mmpqv?Q3WK97Y-N+ zC=Ah=8KT=(<~)5T6^2|?k3r9&f2iiyWs^zPR0BsOrOIy8{2X8>?>}uCz3uwI%?`VNpH}zTqoOhZ7PK!^_i6TiCk&v^3J+ddpj(zW86l)J3^MM3kiYWwknYWlPH>nH~JTr+FuWBn3II#Wf^*;^x46lnN zydh4K2LpVn_*Y!8AFJc?xo(tDMg&A z?kT;O+;_cJ{TY7z3%_(v-3Kdxm}o%i@EaTNxE$7@LY%RVHWGmDB=^ta3}K>; zcu+o;VdWHN4#4wSmE@j5Ij1nI%@_Z|%X#Z*%`5j8qs=5M$8bY?LvX*7`ZG*F8Alfa!^i&e!Xfj1YyoP2o z+S(iZPQSvc4cPl6dlWb;y#VLEpDET(@uLA7^C4?OXF})p73_nJH7u6g}E8X9R%MxcU(y*ekisRpnIgVQCh) zJLb1w-H{Zo0e&PRFex0gPSJV_2w`Q9j_ViLCHJ8qFHTAT3}t{%r;HOYnK_Gmv=gU4 z(D+krROAj8)(k`*vHmrZxT&4vT(A@|4 zTsfftaq_*CF6f}hHvpDr;Bv9^6M*9O&B?<0D#hz9o=wvX`BZuly?)`;db;aHok-uGftn?FfpIT!Mc{LBWpNY%b5=zvz8yZG=~d70^gW^U|K|#k-1BI=vTCB z?}RO|;pqygofy#l7QM2UfbB1d2hY)^EPD=gbf)77ZzCnSj9g* zgK7WON`#?w1ryIGI9S3z-N!R;PYQSOjK(w?kMTKN)9lQjoiiq$(aY3gE=A=DKQx&T zmy-TZ@huKF1@Tqb{ZGFjm-f(lYzh)&;zVAVbAcUZ28pA|`!tRFE86GgC?OS@USm zCILL@EO?Bp%Km~R;|!&=w8T$~t)p9&>%VGy?SW+JO1Q1m86d0ewHB$UhO`D2_xTKe z%m2CYm@gu>CD_FHzaSllOEAzLk;{Aopq8{X-xn)t#o6=z{4STNHWh);U%!J}iT(4pco}ZwBjkJoky6ZEpj?4UyYtYO1`L#@g^Ty9O@iXBC;-ICN$(z0+ck)lF zcezuRA(&Y?Jk60e<&D4TQ7jqYWA5`N?PC4V&sfNtOkAseMtzJi{TcSPB#3+GDLfEA zeMM2{*|Mxscn(^7*nswG*!B5KDW1GV!r>4}9_E8EQJ!>E@|=G?0%Y~av)iH@`+n{8 z0`ab6uARcwHLo6K#H7_pY_{BL)RfalOniQ)kSabFch}$Db zqC_{JN_A)dQiP@^uul|AoOy*9FEY4eGhd6GfP-ngLhH)M5@8SFF{%J?L z0d?aW_?HJ?n^-sn5l0KK`V9CfBz13JA%jKyboG`P7~r!+EK*`{@hBqW(3;7y9K^w! zYxpOPfiXf6vD5XWF|hR?20rz^Xz*b(-OauQEjm5I9y8BIb+Ko_bIOUtBEy5*S#+^) zbs%6x-Wq8jbg@B>d%xPHp`S4`8^d6YIUHiMK`i87lVTZNYok?b_66Nn1V}@wn2J;U zS;hXJ?%u!d-+dDJoSMN8t6~o=11N1zDU6X~8 zG4;}d=A~seBw-Sm^v1fR*Qrz`XF5fyRpg(N!2AvHIpTq|(~cOU1$7IJseOrWDcc}4 z)0YZaB*LLMEM4R1&)7&zd#j+=NxR)`ttaIc6Rpu3>k$$6xZ_qTN>+k6EBawp3r-)Z zxfQ2l88t!KE&Qx*I@)DFs<5z={;hz|yNA;}pbAJZW!K5HPu^`jHTVBjsoEfhRL8c& zx3Dhr+^c=SyQfrdR|%R%brP%^6YMJ2IIkjFJvC!F?K`{+rYBTlM`IrkT|@>$sQ20` z`9}etH|W68nibN7eU&{!qO(C;;AWhP3;Sl&fDwu5M7?skBG&|MnS4_URfdERAnV|Z zp*e77j%*5Ou0eur3bO{Apf{x&F!k05Lqm!)_x(EhgB-3aFfzBq4bvu>m;?Cq82!x^ zKvxIM==%iq^t$|h?QUq@Qf-+D4vTEwfcAV9Rqw?S2o zVOzT^FBwJ?yYlT(Uj|J0-rgg@+^71xvj`N@ z0`O=Pq%g6*Qj%NrE^h&_aO#A7Wax z-4X<#351*yqz&1@KAW-WY{5sVgO=v8Eu3RGQ zl{l>JxO)Ns&fdQGo&bm8Mi~a&z}7x4da#i6Jt+XC9a-Gp;hb4Sn!#nn{}*7WS!)b5 zIzL7H|4d5knVb4RuG$v4h9NbMB$;cp4l9m;g+j@4*fj(A3D~9-DLUB$bnFgITi{y_ z7Ii?wb@vYiK3!oXLMZ0M(8x}#Q4A#LTSFsz6Y~wwhRQRPtZ+oTD*FTA^P(`4^WsE0 z=u=y1SGUE`>Wjm^L_=>I;6lx3?TC$lPp$bg;IkNCm!<-r1kX{5o!fw(kEvQe!;kg= zJbNWI&RxKklba4nNY{L(%}cQFNkZ6W3OsAMvXKDvgf)-~z;vj?bZCgi3RPqqF5a9O zyQRge4AV@l#s=+A-FV!9v0x%ag@2n0Ka6wqMdvACDcF|;%(hIe(j2&@HShqCq19wZ zdlZIIz}1!d6~hW?dCI=bAtEunP@JN2q!mFk!R2>Pr+x@Lk)6ucsS0)9IN0~%J8MZrteE{3^z z!CJmU217NZuRBN`5a>^lDRI!T^AYd~a56=zV9I$hfkl6I%AX}}J3r4#k!PdLkg*On zat3L0U6*Bz-2jT>VK>7BU~AfGnMD>Q54c=7z)p@~Yc>Q4Ke=FHSfzj8GV2wWH$vfT zO_m?+aUb_!b4AVIl7we0fk_*tE5JR^atkX|XlX`v=B7Q7Dixg}_jsy$>sq`37>xVyS63i*Td(2=3fidKQF%+1GIek|Q{xXbD{5*0dr$4}G zC&}!=x}3uHa{zvYmlk9hFkp>6ql1RJxeM?l3ei}pSSAMU5(D=oi*-%>OH8A{AM2;_ zuTA4%4nvK7t;E5qcdygnm2IN2a9=02-M{#G#QDXp;5o(CD8S_2Dg(p-Cbtv#^=XBU zuWLZ9Yvd#-$|03-cz{;$bw%pLNLrNZa^`S6eeyN+CB>rg3~^0l9^i8wrqL!b%_iEy z6iPIn(YU5v)p(}x-r5B0CZ6HU?`d*(7ta6(ii=oAV;YAQP;@w$#WTb-{T@FD@JV?@ zYyhA0Ul{n*;?t{2THaPRiD%FgC05hC>G5s?jpQ~huXVtWnzSl(u-xTMXS}z@ZXz9? zKg=6ZnTT25lv-Uu-xUm=m?p%;dMi=`zZv)hDDa?LJ*G&mTIUsIxLs7_KyDI$8fCq4>Kr4vB;@88MA_eYY+!esWQrG!*ZkA1Sd zI)_dG}4ICF}B!d*}(NId@LE|;kh`x1mae+@eQ;wp*cyMC^Z67{el?g}J7ByPCl$7^k_+NUT#d5p|OJ5TKg} zzfQi~z&>}*z$YK(zxyBV?~E2-$U*P(PY=xU=mN}d{7h=w6lj%M{um#-!}SHwb)V%; z7g8+b(1@MooM%}yK6$RE3iQD2ROhx|fwi}9VG(cH;t!_P2pMBgXGwVE?^)z9GjP+n!F z*?$fS2AuONk2YlW@g`^K0!;le5`SX}qmw>qz*Nj$Nh6z)YOy2*K=(XWMz60+d~6DP zT#FWV6x~u{09VJk4PmT1lkMt}%d}yLw(;KsXGok$^uQ@X_^~Be&btd9*`r{!z&^1; zzL_F%GbPr@yncBFei``0MNXYO z5YDt%-DwbP2CAt31KJN;TXO^_T|?V;0VH-fzzf$1q+>-Y_tieux7jOhEg`0iTBepDwgq+_bLx zQ#%0l3$hC{c^~hyN%C&#H!)hjyA4r2mnzj`wL1D(Em#UITC_SGaN2I9>jYxX+HlN~ zwA-oDPx$i{HqC9RZ%p@$uA>`s1OYw~Hr7Eot^^4#j{kwK9f$Z5`&BF7V{H>fSM4%e zg>5=pyKET{pFv&YL5Jn*l6i>idrrK7ByN@t7FYB*QYE&m-H>%YpY#+9K=2s%rTygeyc5wazpCa z*hTe_AWOl|xmAFNFUFQ2Y$ju~Km^(ZXCh zrp@l+%&?`;>kc`uyQJ$~phX^iqa9>XR#%Z`@Bu1WO|i@~s1tPEz*4-XC_tNCWeBhY zvkXRrjn50OEJ}pRd@PP8un%wzpf!n>UXLKEarqJ^9(ri7sE=sld{-FKO2#DRFTR$n z%iYT!jEQgKi3G9O3he7dSazchd5A?R0uJghg=jr*R*JM9YB8saA>rag9~a<6EjX6| z4ED$(+4E8apJf3^BKcPPIq+G*e|6RYKKZ;Tds0~g4*@J^a6fMU1r!lN24~!?(dYyj&({XKu)+CoGYiSAgk&7gtKXMq;M8{$2^6w}VkkppW z&*kdHfn2>h0Qk&Fj)5r1PtrEd@BRbt+x`91cN@cE{Hb+`z=+URG-U&~U0Bwz6VfGI! zr*uM=mH=Q#q9U}~dO2g%rmd3PyaZUrvPUO!10Vr`L2hB<*2Wr5s6H1%t^gon6fsgT zM^k$$KLZd>GO+058f>(Hqx0D(p6FF_Qbl93&>Tu0z1TYbVaPc=1V|{ z$V#`EWjU2GU-)Bvyy0!bOc7P4Y~+K<01@Cp0$HH?}cP60lVbAlDw zS=*N}7J+!Mk#rMRptXrjnkDwjECy~=n9xoPoagK=(1O^|_?INSK_D?kLc)#)xw7f) z%CJJh!-{@1i!NOz;MO<{s{qKFK)-)cjG8jw%o>I<9%A?(z~?^Q2s)@^8=tO|e(kU_ z?ZndhJfFVdJHNhQ2%kRB1I`;_Yk*IVsZYM9ycHJLiGTr8b3FqPljPH({wX`4*3X=1pEJ;71eE$8T(P2kRFf=fy+|9uz*BRRB-Ak8ZF+ zsY7|$PXTdWhFB=Zvybmyf)2d}Q=;3Zu?NCM;`S?KWDHa=kp8{0+3Z}J&8#Z%VieJF=ukUkEI{@A+2i5!L0=%&iGicuA;9iB)zjB}URZ;^` z%MbWHlh?T-mnq!kO-+DZfKOuj$z9$w1T?$PoAgpX1kI+9uUJ5nJB85AUEhR(xrZkF z?utZ-_0-PM8UB|%q?_|Iki}YEiFO5q0sk; zV$~9bvRM(LVz7@=iE|%-FrROeBQU7i7iW4XYZM^%j=GJ_v%gf|b5g(H^Dbpj&B*H$ zx^j=3vXXBXUr{uFEe#6iH{83k z0t3nb{Yo`(1%Tx-d5aFQQ|)}ce{k16im`3Jka+0K3fw&dDcGzU1Ml-4Q}~N1dtYN< zwJcqf8DjNJt|yHVU92(WtdM$g5(lTaw#BfM7}zBt){GWkkW!XB$Bh{4wY4`5!34%v?z&o+be_p@10-pdr34|;O zV^$8gxUxT<0JbpkYi)Da|8`G9R^@0Glm4}5%a@nXhq)2N(X%dr`Ey|#SFjd2V zEYNNh4H3xmYn>$PHmc5jrlECmN`qF7PaRalbjZv^t{OoZhSm&_D@QXn7u0Ej?$zbNSAi1_ycKJOC|+Sh2I@o^7)FsZyj?gf=M{jbDSP^f8-i!5{L z%i=`WDj5qSXz!L?goM*DG?Y~`#@TjiIk^QyI;CoKmLu>X2$c~Oj4_Nl7-sE`TN>>E z4m^Oi+`=Em_*CFiVjF0Bp_#}*nm`Bh0j+J15wG7(vkzzy;0ggTIXrqCI;}dB%xh*y zM~&pT4iM8MkRDR$1&K1iCpBMjjG3-FM#~FLG6fb+u4yNQ_0^)vX4W!9n=NOokq|HDIWa=rJW4Ctu>b*Qs!|8CKLD=w0T}2Ke$(b#xM-jB02Z z0)lj@=C*515(^3*tY)8N(DP>kpB7q!@v#<|4`vV;{&o8)Ey53Uf;KvpFjxXQd9tVX zv|{e7g7yjwG(a+23e@9ItxiII9P7AC3(fh3u}(IJAEcFqeFOmj7XAl`?Z-Roclsv~ z6()(3aR64tVT%A@WBMNSDfmp)v)$tU5!^vptx=6^LPwpV>FPmRK`ju4jRR;oKj(+t z<|YZ6h@Aw{Zopq!&xVKY)0%}3!D;O+E3IdwMP5~5q__Jb> z!DjfDEWUY&WP}UWq#N@`VITON67$$(&I=oQddHw6@N4{x@mV7Q>8;IxPX$}HV9l#e zkq$N)l&{c;Q@h5e!1602F#|#FwqcV%9B?jy#S;>ZvaT=$;~{`Lo?ja*GqouL*sA%Q z+`w+SJbg1@F-oWcpu!u5*WD~F#pfI@tVuoxozFKxHEd%)&@IeSRe8Xoz0(yccn#Kv~$YGtfSH1Ad49VDz8nSInRo?MO(Z zX|)AV*0WUy5UB3ggdqzP)~Aj0FE;=#o8)3+5_^&gKkFHY2E|K)@8)>&fb2O)f~H!? z>%sou&z1o9bJ=Sw18WlIq-$A9o(&mz(boI`_+0Yy3#J?d*pxvx4WuBNIR@mwsHrMo zO1o!C*x?00EubaI3Xc`3lKPpM zDcBZ9V=umqZZG#yyD&^bL{gN1#q9tPnQ-pub+=j_l63}U)%(&U>l;lS0FMB0nDk+t z^iGlexR4fp@VYocPtrmH4BN}Y`5W*Vb89h|@C|I(yfc+kroII_7#NlrW8eaDz$Uf| z2yCbLtp#wwFBD-+9$hgl7`Eq?28CM4)g+VO(Hg z1Nb~OT52iEO>H z$-2hBY7RhRYx{y2_>u!5D-Ajz^m5f|fS>PQ3OKt+h8Z%ZgT|ssfI9|sJls#p7L3^l z`f48bfg5Yx!Zd#c`|s7U-0JW+&YV2YF@zRVY%}sJ{NfM62=h4>*f^FD_csjC1|xlU zUDh9N$~sA7jPu^aGyn_EF~H1SJfo~E6VFtLRq#IzEuO)+(RfC$GjJEr`0$H;x*kes zxobRA1KjBXd?J2=<|Z1SUl{m2yN1AV9VVAaNyoY*%K*Bw+-X@NB&(V`g>3jOp4&Fh zt>#TF*w5OUS(?VAOzz}hKu?OCX@T5nKF&0F-PbG}WnE&)-wu2(0l4@8l&7rcl;^I? zSy*PWdzrJX3JBIB=Ii0|0%Sm`w|wJqjy*7htqo&nBD1 z{Adw!R=DU4n80G6z)I~_RYHxHd^Cs;kvS+LTcR=ml!{1NU_zQ)hQg|!!6Mqp#`>tK z#M&gv-o@O+Zp%TRE=#y3R0S@(v0mU-qS0<_x^CQdD`LgN7g_Q7E-VA&o>i zAEU?~aG!QKZ`L$^M_+PwuWli!luXWa!o9j-2vM6jc*MCeg2>XNz^{3e*;jzQ9~GwQ zUQj{G-ok(m)uDSRV9cqC5T*g@aA>|TM*sy+Ur`ATH7F6&;3GWC)(dzqeSM;r~bLqACEY9i5p+hF$6(|u-3$kEbY%;{0k+UU>43C3)q;|o*+)6Kn00n3wfta zERBwA5ML9rp=VB!v2l4K`M0m6_!gNEh+~)Ssa)Y|is;oV(EH1;`{zdGgNUbBkd55k zMg9cop+%$=0>n|IT{_=*!1{Lb3=5|NESIJ82S)F~HDnEjSpDbq%Pa6{;M1^o!f2@= zoIAfjJK-w2Pb~f>TAdtH^RTM$Q&cagBGn2eAnsC$(aL2F>k!7?2Oj34B84Kxhjq2n&hpa{h;q(|8Bsi z9ymCED!NuHBE-)x9n3{S5&#|3gcS6Ny5~O7@Kzn7-PmMmX!L zDA4X$Ks!@qDr^9sE^T*TgE18UvKpU1Fcj_EH);epL#9@*Fv)gho5~ita4^-<0QDM( z2Neh-$g}ISOv^`=V4+R`uh*yyLS=h)IqWphbF*BKyLrVB#3YC*Ve#yteB7B(@}aax&8l}rX@Lnp*);)YCu6L4(IvtDgXm_*K> z!<=3Q-%=s@8Iu?~LJ@Id3ZD4@&HOG0j3~PSK5Zlw zHY(9SXM9$}OVXy;IAFk0IEBt8Aj=Nzj;irTmQH)CT2WfC4d_~ep@+adt<_#8xqB%9 z`9ieg07M16Or*zrZa$Cir{B{;QKkjcsGQ4531GgIg>3{lQONS|V64?Ofgzy8{XrV0 zEp+9v1hF{)-xcoV4mzVdXuyT}oggL{!KZ>$0JuzA(dbXEm67xUE#;T6f=Nbd^uTdC z2{NDB!2GAYl(nP9L=T+^KH8?^3ozU`hj$l(vI0OKaNw~>w$%mOw@1$VqP;KTBAO^tKl;H}E@(sn`O8~Gd26zqS%$kxL$0~z%(1c@u z-!nd!*yjrBnKnTZsZI^%pIRLj7{$B>KvcuR&?uNP31lkbSPWpoIfW@jd-w!vm-9#! zWaQ#4aTK~}Mf^;S^(BoEI?&(04AT_}m@tDz!sxgq7XY}3)K_3$E%1B2Bx7~}JlmMI zIa-&SfOTp*d~S?{fp#Sr)@SzycIYu}cc)UTl+h$)_|vTwsRf%UI!0(^4Z|`H1Q{^E z<>4~Ty8x_A@eQCKNNxlHK)jN|=3#tTVS?K*1&`}=4P>Z*U$dDDRTat9mRFc46ib`7 zfK*&*X}VJ448&y@;PWxS=QhBn|BC>hC+~sJEUd`V+={d?Kgru#Dr#cj2fX_LmAn9G zUf!oX`z8h^o@fyZYrDB&eFA8Vi0d32hRXW1qY);}6R{8nk6?;dD7-2o4#iF&}rhCDRE7MvtBz` z^vu_Dut!3uF^w`lHJ0&WZoZcSZG|(=3F4U&j;aah*#!I?0er^S?m#TG=fA{M_cIdC zw;2yaj;Pe9X~>6kA#kp1?vxC<7)~?GoxDIKLy8DmNYMPLIu7#l0h2qK`3RafK~M+< zwBTVQccNH_EYZ06545@);FISblEPGPS07ANDqMX4# z3V~C!fCsf}zAGU2RZiXkLLQzX5m7)!i9By$jmZF#OL7J9a~Gg@XAzbv8vlL=g*IXs zY}^LmXaQD{uW3<#Kw)lxoQ4+Y{On^bI{N7D0PU1cIYT|-ny#K(Gt}zcB`x{abP^Kw z0i|L{RRC_@$KMf(rCvQ}*iBmWu+l}Mxy%%E(lHSadWpqn@wn%;-9`8^vDBj28 z$fb2zllxqzM;Ah&g6x{+GG$nBtAH9$|9HkH8WrGnNn|G=bPxlCkcHSvh2&(4Bg*C{%y$n;j{uDDlJ`5;zeg?7UIrC+_kle-Md;ySzz>4sPPM`+L;{=)BLHijWsN zjUu;-E{PUo3340i;Koz}<>PKSm0toK;`2QKdvREK3!7{BnN>-|7^ZobI|cBgR&t8_ z+^GQosd6j&WiG(k7m~#&3`v-hD$U`xagIaAQ5>VM03dO49|BR2cwJeaI_%7X-?k#>qD6}# zdZz;hDPw*J4B;THUyV82_>2ta4zC^yfG^)3dsFg*9OfKLTGiAgNP)@pW08*bLyiv^Ix(KTN~kA8I% z<}8E(H-&f)pTS@eDP7Qs^!7-u4~`|9XU`C}7W7)l$vGZB@gkVvPAKTf%|YsFLzefD zG1x=z6zQ8Jz%ro-I2=+3#RF;Q-bgpgAlKWhyoI3kCe!8pKfvcF7FT`+_|)LU!_B6m z4ssq|MGFnxr#YO*%A7~mm9an-!%qOKN%^FsKoX!3!q6S8kE!FfL)BJ277g0HBwk(( z+<{jHwNC(_6=>ylst}0dTd^1{lEoC&8nnaG2^enyz!6g+#37LQjG;>_fHfd-GP-tJ zIUwj#jZYiZ64gnW66B8S2(nj?0s5Ja3>Eq$ORFC3dNp*VetHo#6CkPmdki;Hr%&*& z*F$i!xEV2)US~2P!GJziYEBNvi6=m%KS(<@j?ClYP@Ppse5%pMp)er;9chq_W@L?n zRUK8gORxazNUhtXgbwG}a6{}eY5G~po)}_UcD&R$?!|;faygH%v z!3u32!TP1GD6BDB!(op(0n(hXj03D)jJ4DC^TXUT397Ci)}kzF96amSYQoH{GB5<| zLsP$1YC1{CHG;==7#JTX3K~3cr#N`sROdqTv`vu=sfO+X0YoYhq{W^C4px_oRz0_} zRk_9<&qh!6uy{69%a&Br=whj_b)q-r)Ye6kzpA!{9-^c44sdWxtH$P6V%zyj0x&+) z{O&I~+~@~{{l9&q#xMh@|2}l>yc~1^DyAutS~F1?a5+K(;HTi>BT#l(D0%<_s`cIKa9ikl)q znB*_IwhHh|@(`h#8PR|pz*m7yD#KPy$aTMxIOu-Wp;eS5@Xr|gOl1!0XRoAj_J;d; zDOm!5D<01)0J{c3z?XbY0SG3515?)`TNHNEc32*4VZ0~M`?T@BZRmU2Mgv-_XeH;^zg$Tn&+%oVG8o{A`{;6aY2$QH77dHs`sWEfe_3%lbD`M51|)_;NbHNo485) zXoJtVxhBa|qs30E{w%!)WeqB{pNvz!!~!GNVhJoTojpyGfL`_zofm5<6VPh=dH`Tw zr2t$o`1-1UM-tmYw~H_yCNOdxPS^sN9k!Q)b5P$oo$(D!S%)RrhUs?>TT7>Pp1(Me z3Ob$j@QSpfYv`jbFqk7ITdNrFZi)_W5-2B~7GwJ8$gwCIvX%g54lO8wIyv}Ehfald zQan+q8O~ofYpYCJn&oLhg(U`SPpy_ONd5?+{9`0?XnQXMn3zgCdTo7*Z|;RXral9j zrO;<0(A}d?`w=~xF$#9t)@bEMSn%OfT5hME95F~vF3)b{HK5#E?25MKHhzJxNt+r0 z{+GBvpYb~_uv~0tpE|;;3^H4h7(RQGHQipBlV!k9Wr2A~UcIopb#hNnpATpN1GK($ zeuH`O(Bkz0@VN-% zmvP>7a>M@8XHn%bumRecedGZ8Fj@wHAS2EV;c0K^bOq zrn+dbc-eDZk^&_Sewae5wxlbF1Oj`>i^f)*;`z!_SXN0`l!dCS5--oBUbhZo)0WFV zsE7*l&jv}r=+(HS0|TsnOnV>g-$jNjT#w}7%Eo{Mv`#6C&y(P#*5)M|3-VnF8@WU^ zaR98TQCx9*JQ9ZWtQO>%w*mlJ0Gip5rsccJMy@s~^Z11d$i z4vmA|uOrf3iyF4%)|;QaP0X}^&vCxrD zuhNA|?zBcX0?*|x^;39+qsQT=0T1qKWcPF#FN%N<%o{In;4mEP8`@G3bEIw&p z{PtFNfKM$nOop_EqE|oUneQPB)&ulXaC$_S(?pq)_-tiQYK*1C5*vhuEMzJIXxx{IGErXi>tYb8>i$k8jBCfWtzm}CYK?e7$9e5av2y} zEjke{vkVH#XfA^m^D^BJe+KXg(1L~xF#tp$ba#VDz-+82^BRza0>~UOG3hXb4z3%R zbATTja8HLk|1-VIna-!t_Eo7jtO3*Y7+H!}0N5`vU4t+8Qs7ey9J3tM>G~-LO`+); zqMO0Vz0$nNOB{HApF$wm4vJ5DpEURE;oI(W&IWmA7wNDbxqQER4#-8(syrrlg3!=p zh#|-5PcPQ*F3WYm91P6St$LR`C6QAyeg@6RD2wP}Z)o16Hc%V`B2S<9K7LeL#;PZ* zn&KvJ!q3?0oM|Dyp*|){-c-0?ND@g~g|o0hi~7VH6gNPiI}9*J2TOVZOD*8n=7>vq zUX)$Qd92{)g!5N{>oI4tYL@bm#)#~R?TL1fU764|)2|#M!}Of#?1yqo+xQ7mLnllI z&ou#NVQo)cuv?KTo8zoa(-xiPSxn8-)kGd~5As@7SCjZ{s+#*66cM8#TFN^GK{|0Q z%oPBsG-QK9hS)4aj0TvN8As%L93@7hI0B%kofGRwR4n4t+&;)Zm$)R?%fE_5&NY4Zl2j))b`bC4kM0XC|9$6m2Y|CayI`8ixenNc6ys`gn z#KH@7A8%<4yq==FD=1-dh5#|J#=`e8u*Si9J}6*p!88^&F)*>I9n*NXsF;CM-jXSE?C*51l7GIY*7+PHW&eJDoXX91C_IkZwV7j z0RRp?oxH6gx%$~U#Zn!HzE3#J`b;z%RF7~TXL88+&;wBMBifeB%@2Y;QmT6=ZGcbJ zIpe_Fe6lX9TM61vI}&!#Fg%2seggaGuWzLAE$x?7yE9yKhHp{O#7+JEzr>45j?81U zV#CPVdK{OnM{(%>a}p=`nnBwN? zyS2Ll)Bu>v@ExvMJNNPqEsoD5y73Gr2gr^t-TK{xw0`kB#FCLm$kBodQ%I+}A7sXI z2>WA7f=RY*9JoWZ@u1!N7)TH#pk9TwxCwZ+M%689_8;QZ07f>hP4^4`H-UoQviqs$ zcx$TNSt>R;BeH+WXFWd>VhhZf@4l&DuG2?KrS>?N~Ja&$Ge$ zsgB8zb{yqgnRP1~TIBJ=2$iajgwX0HM zz{Rc7{9m6*b>NTzdUO*8s`vmt7nJeI`ft!W?(Sdk%>IREmPwLr7z3`fGy?xE?co14 z6}6(9&$W=TXplC?ei6wD4hCAN6!`p}iMrpu1!Q8% ziJkUByN4EEl0H4;Qj36DBzpeOUfFl*eK{I}Q}xg^kk zn_-gpl}=xTsep!_Ew%-zh-7d%CXczcuT}{L!ie+3KC`iB^x9r9G5I@~QEE4|RegL< z@BJ&lAp6xhzlwi_-Lrr$7zg?`iQg9Qa{xWMSC#Fd`_=4Ky_e>fDKf&n#QIGp#h(Aj z#NQVT0J>o42*EqO8W*%8*ZTnZcRuO&bxnK`UK0}Jv1bWwxMlRdY1!Y3pbN)P0SYY( z6v#AXp-xQr$n&PRjPI_2ISv{ox*Fe)XWs$~%?gM&9nzjy%}PCcCUp|75-rl168e?2 zE#{bhq6}@F(v2G;xpx!TM2va-1eFv#QtJ#kT0^pfHn%wYCCZW?6wLkgf<+unttAZKt<0JT}<<4uKxG_3m3)u zn2DF#?(3ikXjfM3oXjsT%PQ?!tJ~`Y?I9S!h^M2W8bEAcr(=#ZNM~V6@i64*U;w6Ov*nU;dg4EnpvPopuHyIMQwIJ(T zw6WjGFQ~)~WhQ(X@iFV6(W!a#hv<3H2F!vL2KKNs2x>=IOyl zPw>&>78B0VIXWZxcyY+E8tjsGK27TRn#QF-#$={Cz8NHm6!xJ$Lryo9F7(N?9v|tpA^QXBv_hcO^2#|$i8L~IIRsxW-&!S zzAw+EYCo%4xo15zNE=y-M_7sW`15Sn*mV(>CP|D+Zsxr}#hDt$Th3_A2nHH%H%ffYXR1SEu`y@ZSel6dx~a3<<>CX6@|_zjZ1Ee!mY7_bPCct$~;GZP1KK3$eep=iL6m?)a4 z>)}3UCcX?U#N1!d;QW^Eq{>w68ZZ1eg|7dz06Q9hX~gg^+V1-3S6{t3eg7;Lh?&d8 zzZITY`aoeyE#Ae!u$BOnH2&2%I7B=Hrw#-B07xG5JZk)H{42|#_tN+*p3#2gok^ZM zfaKTsxI^OKLkharC9eDZOV^%7)RuRcl-@`jsRA^VH}}y{M&mFRU^ti6m}>nGDc}xh z6Kw;i6p_xz-lDYzXr=7gBE!W>Xva37FKJA!N0xamOq@N7X{_p#@Y^P)A+FKE5Q}Kb zE-f?39(I(8XYk!YiYrq+>+ej@LERs-n1-*c@eH8ngrvQOZtd+WVoWTDj%9Hgks=9u z%fr1ft?sk9ba4gng?Qc!Iw3)C9G%bN((j1rkq)@x3tzJjYd$~>bmmlg!^8?nm_2tf z(HxVD$JG~TNo%ZdC}%e}RV3ed{N{?ok4 zikZBLi_&*$048VB+-Zbqca|Rj&qp3S?Oqa+5`z_flq%2SfytXj=;oo190XvcXsz*c za(QDmU77Ex+JcONV{PaLKu>#*VZ>@KGbNYtp}niIx5nTr+dLZ- zIA>*K!hijpn#&N6>oqxu!!?i5d`9D^8QYz;U=1`DpT*+}KGjVgqq$5CChWVjF2LX{ zmx&O6Y96Th%)Q2^$py(}iqB~;B@fJN9*BR+@Zf1KbHnFFh%0Bg%o9_%SR;m3ediUl zs#T{|^WRnu*%0Ck6JL`5s)Wb;7uQ17jElnI5C{0f2?oxQ6KyQ>Jv1iv!OW!qLX4vl zC>jGy^}%Nk>9fXDG5b|qn}x!lg_yfmLOb<5CqFV_=0^sT-2fjtxxdlR z=e|vK+Bt2<%qZB@ya`*|B`3s>Y0l)qT-+~ZHS2wHYAmQHipDU=+30>XlR=DtTfUFi89iZPI-I0fj#sd7@S2 zoB+w@m)9k^#E|DSg@IUzZVEc}upno$mCw?%T)mmzIQ4BNm(dH+9Fmvr&n}bMbwYY4 ze}MniK|up~m*u)^c3P%&&#OEP1L)?`5=pJYG-X&Q=dO0`Es=w%{=KG74$fKajZ+V7 z{-VpocOJs>9Fdm}cvo4X>vQDUV6)z2kc9D4NM#`FAizMn#GLFd1?3^S*&8r;9Te7# zndgn}Dg6w7pOu_tO3`CN5mATu>U|s%e5-Y8fbf|Y(#*V;%I!605W_jqA-kyAkexH% zOurr1&vj`LPUB!KxwvTJ-_;$S#S}TON@d^|I2U8w=g~rRDAtEuU}k&UjaVveqIj} zFI6Yl3aZ87K`?eb9i;6hL8lGdc6e2WaoR>bt6_^J@z3DS^0Sfq;$mWLu7LF%nVGf2H!B-m7RyuW- zZUS06-+{02)5~~%btDAlR2=%%Q|t`8kay_Q907cupoLH&F#GAH4;+UKX0wCnQ>9s> z%|`?4G|88rgD!}<2#hfzJhuf;z1 z6mi+u10aC4Yk*I_H+k1T^fIgobg~ruoB^LrK+i7KL@HP~iB(;@2g_8RXh%bjBS4E( zfc0x%plL+HTYO0(h0aixgo9&QvaFpOXB}GEqe{My)5fvT7NMQok3xL7{ z1e&YdKMp+h%xUgJN6F%QVScht9hf)9gkV5|O|<{kIRIirk0ub(gp1Gn~byI#z)%*l}odIKmH@Le*#RHnm{N_MH>xW4(~KU(8B*`?<^nWIF@W5%}m3r5u1JY z?!N#3^Lx8{uUQrkGc+^2-^uD8uq9g>Ted7`*`c19?yjmlm3T7Z1j#67AP0*qgi1`3 zbGY`q#WB#hc6s$&Ot?qXVeKMzkuNwSQ~Ewh3XUFa;Mo?jHc7?$;GbL}^o*VR)m+#; zgG6V?wb}N`cVxMTyofJaJ@chCvj22pv@@|PnTqR#_Y(?>@Y^;duB=3e;WC5d5?0JI zP41kzL}L+j*1ms6#>S`nc+(!RSi&6>(gas;#ydz3h)?IW%iuD@WWjlR(|w%=q3*=@^F8_Lm46TLp%sx{%GefACn!Da9D^>W%WKAF<{TG<7tUk85&A+ zbCAd^=x$NZrlV*QR>>6T>e>_AJw)GizsIv-ceP2FR%XQ_#oLU->r7}_%uue+&~;gX z+>iIp@e(F^24#EnVq7-Lct`@aR}OOY86)azsI+X+(h8B8)YpX~3vAU{CP)wjudA%pP0=`2CS9$9j7Jc8_**+SIm9Figxx3pW%|IWIKd@4L7jEHe9p%-E@#a5kLaGU( zuHC~!s2Q0fM0OER4!jX)6Ay2L3*zbvc&;Y6uA9*Q0Xskbutx-h`c<+a>%nCZxy@;3M7J=B{XdYn?-0{MAz;qklFxsM4_!cvPdn z2N9$XAx5k&owd)==+*f7HDdq^-2>ck_jyOeXN`Vsm|JI;pN#-C&eqq-jQn7h1S>39 zkiEcJ5KlMGNBDf7P^z9tzcZ4oY#rHG&rj{^X9&C>H{7E3;y#&_JMRWXg5XJ|HkIW0 z$$nBTFTV2TKLpQ?3dL76*=KSuFMB*IdXi9~ehwogJfqM($9vCVdJlqrRU3kYv^s3V z)KBV?7JRgc0b^nZuelcbr!71ZLwGI3b6LVSspo{hN4RNMmvu!Jw9@|?)>wQ&ls+2F z+0)?)TI+btU-kF|IB8r5hBrKCkl`oCT{DLQS4W_!9?>?W1FB7khPa9;{7> zb0xJJB6l!y+G?-F3BngwWsC(2WzSQeR>?bc2N>6WxkxT!awH7&We{2*GozI>6AVW59qD$H*803kucA#}cf^q* zDc<@R`^2VK$uv&Cvp$X2D;wt}ycQ#P$42nh>!BT3J|g6kMeLCb9nNFi!jJIeabCf+ zg*KiL9YPj@O}p_G@irW}g)L(s^O||)F9rS@XW%}Y<&1Ufo7 z59ajhkJ5yJGA|VdMUt-0&)~T>o)b+)&}qksBbb*yC3A7Z%@v-}nH{!s)`uvFuy%^G zc3N6wT;!UcvVK}Brg&cT+nir(CrMCf-K69?U6{{0WM7T76OLyW>!!3izV<%zl7BOA z{N6=EJlmJoE)FB-;7D$SR}!B%-6fG5>Eof>Z&v6R>01~9G%oaUY!NxGzQnab)^9_} zXkcwf?eo1&JKm(-Z$R!48cub_h?rrK1IK=Ggh-MS!YeCE7c_Gmy(n4!NB&&XSZbz(T zT%A!w5v-NQFqzgd1?o$Q_A@sJnLnOiE|REEYu`5W%f`tw+hhIvY>&A=Ynk2Cs{pA5 zi~P|1hR7fhXt&pF94>c)0e#SFWDHfuXXojtOBE`k5!2C-yDl zq^u256leT#^BLCBjD|ry;ETHY%??!zjfu=ghDZ-|i;QO_B0%UnJ|P+hTfI-xJR+MW zYtT1+ZbKqZx@9~CH8($n_&h9M%+2?IMtokmdu<{|MWE5okOf^5dg^1eewxF}yhQ(^ zbyJa7aS3V*KV4hsY$I*1faj3S+v?r{>!uC#8(-Qn8pp@fKWdQU0LUuBPrjGDOE~=~6coe}-m3`E~UGQWRD2Kc*ZfluZks&|)nF}a(oubS%z zbEGaZZ`JK@AU?lkULoR>O!KcIKHb^n^(fC_H`#Xyvs-3CyF|y8XGBv-hC(wms40YM#KKEnC)!1qc47+~ zLVPCaoRds+qYAsg4Y*s~WVd+RX=xl%=?{pSJ|{G@N7z-+eN&mGJpuWdBnz)Xii>#H zP34$$F>y|^nvccmEuU%>&&!>y> zu)AG`{H6ZO>Me-RYoEUI3j?@8U?_wyOekLoLU4+Kw8&yeo~M~{XjF=AH~Q_%Nz9d^ zju_-dg!;+)EQISUD9dQnR3M=nJ=!0e3_~^>Vn_z$5swe>r!BJB>((}Cw>$?t2ig!M z?Zho~7S~-{bi_8B8etagWk{-18U^bIJX;dnCt~?5meTCZDG8IpD#Bo}&BSGkLT~dR zD`X2~TaD3+z$FOY{4^dJB^I<(7+E0YEw1OM4G-b0^0~>v)%q(UO=KVw_yI&(yMnIK z5u_HLe5x}vc{{}=yTQ;GLDM!xUCU73LV-;jSI@#E6(S_8g@zDytp}NKT879aMY<*! z>)~gTtS3E$+>?R_g zfGooz-n$FvQ{`V*%ZRcl=iuD#|K|1yUU*@}Q0;RwHglbwVN0@mq41ap9%vUo@HgSwC~7j~?Nzpj~0L zyFr&wyn5N~3OCNtPD0D|jD_HNlQ0=2A=BLH;?T59lLHpU?C7QuSI^H(Bb9(yJNOmA zFHe1uzNIFZn$T);?>xS!BO?}^113kE8gd;-TC}QXB$&aIg9HJ`WCW|XwF#jyL7PZu zg3{inne0sy=93{56F(V87HNum^Fz;3-fj?O5C{s7=~hX`1sPawC-5jskt~Feq9UZq z{2U>^^XL{Lt3jFaXclSF6O$(<;aX9=YDAtYT&qX;yF6N?^``}HKY|wmopnfR`tEObu^*I1esJ6@^d^ zd3HS#4@o7eL;V|YO`wNll+o@L&h7jRxj!_yH3n2QejYNG629uj0P0GMer<$z)`-yi z44GfoAh91lT(gfKvhc)9M2RzGaUw6eY#KH3nrR+9!Q1MLo$flxMHC*1_vzJ@If)KK z%w58?uXHx&JgMh+ehZ8*zsuo>3py8w3neV5urXI55nvV1wazs;rCXbDd^4N#YJ5^$*$jJ>pe!H1!LP0C9^gHU>n*s4fu|&?rN= zoHO64p!3b<(Fq$6bc=Z6ybe9iC!hNA3}s4d4oA7wh0`ka zkN&?nl)!0#7s`MnWi_(j?w<}FIk0!Gd z2jS*31Bm)29(!eQEN`Pz$2F@gm}`V&chDG2ac);;g#e6Nj{XO%g9 zl_;bt;m(seeH$5;sOF{2N!?rluj|R2D1&#;2swseUaGkQnYA@9W!_WnvEe}ki<9kQ z{WQy5L0-D6Tvvp(3uRY0J06#AzDZkSAa|k4P2>SujCGQ>NmBjOE8)tn&&>QH~o*f}gK;Ky=xeUWS5&(<`tQ`j<>c@C3lR(aDVLbny zMSnUAc{EweM6Xtii<^6cABY|RSny>YrRakrt!2oL%vvVJSUXF7(OR&?x>wN)S_h_B z&(xSR*EEi)&XE6rG%Ic#t5D7q^GYY(M7t@VGd!s?B_bfYXtk=&bVwR|jyB<>&YZFS zy*z}K#;0I@qns7RImmnFHU;Xj=H^-IMUFvM80(v^jex-=%KTAfV=Y4kK--kE;o-e? zNInfLhJ1?VGgHiG^7LQkAMv*?b~Zzlk#JV(&3z}1JCQM5eL^wetB=MpJ6==`K5j)UnGKzb(8v; z0-K2$HWoEU%rG8>0Z}*xSYDn&^X<{NpBd6`w@9pdu65HkgfjJ(m9Sg?OXstvj%qxb zMpz3ga#a#+Ir2ehUYe)Q7MOpeMOgDujlKHDwd#AN|CUogkLZ?;t24W<&K#25=#WmT z(g-`fPQLDr7>*Qiq5SL(ou6@Ug=ca z=CdRad(4IVteYk2q1O;)CnJQL11racoBwJKOcX?wIX7{vNcZ5th8J>EW&&9VsC+I^t}^D`KsRMBtkp zD!1CcjPgw}Ug6Z?tb}ZXi-M5M1eq=+s`Ut+ zPI^dZVI~10AC#;}9c~KkshHCdW8p~)Rw&gZoXUj}u=wa=;^E~X6J>%gr|}U4!2o7l zCLO{CLR_*IAwEL}yI|@{2ZRPiG=a84oE_2x8i#aHnh2!mq%??%@C*o^tBr-PyG}z7 zl+H@7&Q!`F;Y$O;J?ex{i11WW6oqYH9y*7*e3rAgSjSmtq#?`FOu$mw31U$xQ;5ny zBEbxF9VX))wq>Oy)=^{O6k58xABP%^c1x0!2ki|ysSJZ#1`%m#u1-2zEp~2nC>%gu zQ%sTugw8ccMW?LI)jAyyA!cbhc;Tmvt8o^-QFdd5pTmv;%i$;Ncvg}JXoFt_Z^|h8 zC^7Ux5=lA~X7y>~kDE{$^gF>&Sf)gNP_Kod>=({>?Mg#%Q&^TOk1~8pe{ftL zg*d(TaN&rQ4>CicJW1+kipfh7Z55aJLScOrE=r@InC{Ta9~$wlN9QYp1(`(!E2m$pUk~w zR9sEBE*dPj2MZQFxH~k#9TEuc+PFIn1c%`65D4z>?%p^w?(Xhf-f!=-?>XPzcbva> zjQ&$&bk(XgYsxd9RaH|zn`lory`MNxDaBiptIid5Gjh3){)euupwt5s^wU4^(WS|} z0Oc#0xvawBXRokB_1_LMqE5LwEB0QNm=47rssoFZ^`H4Fv~aIys=`A!Ik=5KffG=F zX+K!R7xC5CtFlz^YMbG{5Vi;$OH6kZw>#(}+P^t?p$-?bS!S=QK<83R!*&YAOMddX z1>~%wGw7w(i;IZhUWiR?^M6s-b!4}_-@{n=Du>!2Of7-a8f+n@Fq47z1D8!Y^Cfke zsxg_u-0k~eNd;-M&r2Xcli9*CxqD*!0TLLG7rM=nv|r$kQr^)%420wS_CkcMx|mlh zvPEeY5~oeGaVUpzn2)&DJ3Tu~s3uuxYlHoPk$h0qTzo{ck{7GacGG>jXWG)3J>6#x zLbvhMK%$S1sBC%(Kj7O>jRoKO4hzM84Kjs1I(DDjkz$YE$1{xW)oTR~7E6#FUC;oh zG6ylr}m+agC6!4%S(zSH#Sz*$kT~;iu$KA_kb@_|O*q z#1DF5TN>zelj`hh_qVfn{24;=LJlk8a4R0?IirkdQhSmgKF$5!8N-6!kX6yCw1K8-=%k=)xS59eE6-sk@bUhADVHWR;^YGtB zD1tJ0z5gKs9dcc2;~o5>Pe{q;?jUxLXP$=+ar3=u6FAR}9A7n@6#uBviaswR-2(1? zAAZdL*Nq zrMhS$@=q^6+02u==RrO#cyexCAQY4PR{1wWhNMY98t1}&-}AY&epE`mT_6`vm&m&>GHZ} zn!W2Ej!J{(HK5y+xd(oIkekwLZthPC+=)O-Si)SmehZHxcTV_J*=M@`L4TheX0eoDaYV=P-w2z0 z26C4$)<}4@;&!{~UUp;{i(}m6mjaUl_>Nb4{XcmzzXq%^@|o++FXnx)15dtmg0Gnw zhCgoc+J(tuCYeP-{Yb5@;Ln}?Y(hpXo5YY1e4H-I?$p>BF+ad7hxajR)}Ig05IJ5+?A3Fq&m z+d3{oR(6r`Ew%=&NmwtwcjWWRH*iF4 z-J2Ivl3bd$nGPOO44+r<;)=YH`U%fLHPtL*Sr9^9hQTC?<$#jRe=COD?2dTHYg`_X zG*2H2{*-6?H_Yu(@HV7$mc|T7zGa71*CR7C6oa_(LFw_rzQvW$j8gkY%Na|s17GWM z)S_3Gdk$d@4SKAH2m)3V+j+DD;?4frb+Jnkb4h`8%!3~Cb|;Iur!0soHE?E9=&p>% znEmVIWRV9ggJnLA+LD)AVx`U-C&Pb_!nXQVbexLa5;8EvzWr%_e3L7_C_cVSk@P1G zk`&~y1T~!+7g>Qx|Amb3^lET`bz;O(^(w`3uhB)Z<2LLOJDdwbhWW$GrzcfCqt|Wk z9-?B$RE&k1HXT-n+KN8qet9>>~=Gu3fBcM0D=HHZ2 zDy4!R4{#YQYhq3EC}#EZNvBnkItuZf!@Bli9pFM-pWDnLJCZ@@P|T=c&uEpr!Z zMXr=Kzj-_~a`5k}UU)uKwuwyhxr=!|F?^`!{Ei+iZh&0{a$X&}tz zxnl@*0ZKRWhdB*$3^nFMDpSeBr8AppUDTDY;SnEU_@H-H_Pv$YO59Xz&qx;0>Lrc) z5D5uWVG>#0)jJ^B4kf0rxHLZ+hf9s~q#tL#DJaG`U1yI*eGG?#nJb;@a9K`_UGJv7 z-Gj6)UiS`sGDkhkux-v#JA<+p>^J6+S zzWxBVB_m&+Yz|a3us5olSQo$qD6=%eE+a318_4n-sy?*@d7c)iz4Sn?g|LN~c6IY; zcFGGPcPWy05@(PJSr)pg{S59x%q__23q92<_>u4mIp_zzIO;Nu-1Hv?}mvt|ZYa-;l!(Py}spPH#xu|C%>0ZN`N^W2vxgT<^Yu6Yh z@BIKdYP9mGr%R(J-4Mhxnf}%9UNz*-1b(iGdwE7bZSCq-x=|{~h!1S=-uRw!U9ao! zaG>$=4s%)7jX zdfe0hx(nc=S24WaI7C!L1@AmoU&GU&3$l8vw<7ipWZ_uu{!D~*Mqgs>1q?p^)zQvIZ+{&!5QL`j@(`p=uu+iOEKerhj->!jLCUa||9-%OgR z?i9Y;-&fWW&=RIOBb}wSInO7O=Gb1JV{3gYaA%sWdf*`8)@l9FKnfYEu|2+ipm3Qz zTIZ}yy5tzJe5IqsOVw()zZ9)55$ZHb+zXMs-0MYpfkD@uKkgTAnhZNS*56JCQ0j8- z(U=vqSITq5xn@#?_5?f<$!$+Ax;Q&77a;1v|M)IMQz)OA>_0mz47LA?{)YXg~Q9*_RWQ*);K6G0kx44bIS zod#Xp`WtR-kN)zGMRU5RS)6U#p$c#+q6Fky1Zd(a7CP!!H+-BaK~EUd;crCOQGTh+ zEGx-EIV_J)LaIUIFa+ z^*R*npsIP-(Pjii70T<@iVKP&;Vt`~CcD1tCH`QNzqh>4miC__^d=*}o$b4$3T@F& zd(keQYP}*)NpZi(YI&tAb#r^fcv%a zX(_>{A3Q3Ej#Z?sSA-U&_?zxPPyLnI zp!smnp+igz#_4agh}<|$Jd?{LOma4H8VXbAmY-ATxo93L%UWp6S#rtV_8{(AAJ5a= z<#HaFkBHsi`LVu3zOW(Zy64D7++JN zm$m;gX9Zbu7;h$F-CE~zapT)ILpQvmBR+RtlalDoZ5{Ik^8A#~sS3VAX2xr|&=JKc z|D&ls-X5Mbm08p6w$9xo;S2V%za{B2{-~_Kn&5}|f#=-uLe-}F!$XblKqqjLWy+uS zbCclUBcVp_Vy>N^8Hl@YadmyLYgudUGTclBr^5IxE?6_d^9ucXNC4i~olO&ME;UJM zW6sp<6J{le++G_n1ZI~lb-EOzD={#9I{jnu>mK-U6V!3*zsnvUF_wKxp!CHCaK$KH z;xdszn(DOYYWbse(ZBiP(9?T`^Cz>JekA|>&~m!r>t$C$|ILhG*u_=)dcoeNj9`1V z+G-L>CBNUE*TZuYbNe6HSzFkF9fD<$)8+9`J@=86?`W@Cpaaf|^tD+6ox7xwvd8Q= zMP2`!0Pg`Ompkybi4w`_3TGo?d-2mD`c2Dl|HtKot7dtW`F8)f2*rm~&yL5viyJ3} zhOQj0igxjN#K-Cp{^mzP9kQ>Y3w54#PzU$#?1v#WmrnW?>O1v`=^0ynSm{VUZO5Jx z_pw6FC#`lvpZJhvUB)AbHDoIfCU9L%`-5&+vhJt`>5m4p`aQ+f~Kl78@^B#c4o z$qGzmEGQ^I-kJd$y)cd$i*StP+Epb=KoGfliNAI9h><{`{1ogD?X5cimRGm1`u;ZL z{A=cB-qyXUV{Bk8{2oM1^oMD~4e^ZLznLy~1h+pDA_eLBnf*}S+|}T{TG5jK)*{(S zv5t9;(25J?4o^Eu*WuT&H|%u{lK$*V4MBR$V>c7o?0rg$!XQDWFgL_JbPs|biXxN@ z*{%8GF=Tk>e|TI6`$cfpN0k{hd{(C zDd!UJjH|dpL&@P;EgBmM!-013ybH}H&eb~Y|NV9UQZiBZWv1FP3nLbkaNkS+OKw}{ z(+s`sLPuuiVn2p>>(+2HI2R$EW@N4X8e;a{IyhA3kB+cZdba#Ti8w3e>A7hF?Qoo785VNA%G zASuI+5NT$2X`8+n#nLd5Te4CE66kk#MH5t?$@f3a`sPB=u2R}arGHA3Ymu|o6Cm7K z3JtA}aX)Why3xam^EPjSqIww1=DM_1P{<$ zD(-8x1myfkf7~y5O2+DU{%k^1GAH4k3sUGs$Aa>dO+bHyo;hK`$S?HOGO&&8+hf;U9D0BLIf5Ik0Q4CyRE)I!^?E!81!r40 z2_-?NbN~hBIe8TXDeQ3!-T-I3BsiM?pvjsv$4Hdg8WKD^FUmy6Uc@fKuO_uXnX%1T zo1L4|%a7eckS}5>2cqjObP1oQk?vJ+Emfr!Mu-fck)alriZlMogx2tc_CR|kW&I6H zf(cb#I@D}llg;CDKekOM__lBbzrELX zR>KA_i$qv|R_gjT3fWkdmXREby`B6vw;(~P7)0GLlkPmamCEVRSopC%-*#Z(uVqvhsJMS?#c!YD7dF#uVO*`^Vv(5|akgj32-KN+*nn#f@4Yyy z_YJ%v=#@_F6~@;8*JJvhm1eH6U0^I%${?;;!PJVxUfe_rN=59_4}4>hjWO8Yk`wC@ ze^M68)h6VOL0>{gaJT$x-`-AxuXqS6(kLg{nyYObNSZ$6mOokG-O47rZ(b!$T%75U zfB3O|ss7203iuIG4=;Jd44^FHqdNI5M}o7UiEU}Up@G-$A$(iJW1pC7GSfEW%Z8(i z2&Du|7OxS@ajQbql{M3j&=a@Bug`}>A3oVcZI7tSC~Dpv3Uc1f_IKMpGH|6W&q#VnhxU?Zmfsyu$j#s(V9y5jUvb>It5h}!9UEPwHAvd;N z%v#&_esNtT@_m<>#q7FvAx#OqyO2J1lEKPvp9S03Z_hM>&?Hb%owmYXdPV4dV|b(3 zHsNPikjJLubA5Ius786#w!HeNCb~BMh{~lp$s;m&g97!W96|#Lb{-VunvkBejO-H@ zMs%tC#3ytaIHe>a9cklY<}?$J(5=+`V@gSsLTSz-M>1mf2eE>@-`TtXzOx|r?#BwG zmbl+BBKpf1qXYw@7h^U@86a8M-{Flnv53C0{SK7)U!#AO|HANLV(zXn(^v(^ zqDV>nSd*4$@+H#t`Is7So#66V%$p9|gS3d0inBOlzEmNH!K7xFm2c`Q$nktA(EDW; zJF6oy=B-zY9OK&B!3skfPhypBG>2t)SNx|DDsGRgR%(fbmUIE)C^gEK=%+C)_uhDO zc;MDW_M7)fe=8Mk$MnEE*^wbXPElUt!q1sxSXww`uusvGs*n_;S7!3U+nSdd+>;fnV>D%?=wj9StShRt5p@ z2Q*FhL42)r_YGEG;x5=0aIPRWN}z~C1+$;CG>0DBPZscMK#X0@q>e_(?#4x18kZ}C z1V%FxC~PGZJbWCz*+`Ip0L}3$2iv2hheoGH)8!?emFh|!kf!5KY z%cPVcrpN(l%CQ(>Ci!Q zQ}ip4}=u&2l)4)-p@}H6b>pjn z_D1dhNQLYCPSv&NzipOaocHdPe82E{;)RamA{+&7x&h&@2x5DpKS-u+mWy9i5h|Kf zQ?O{J%NV+`W_9r^I1)IFH_mswf_C@bOT>bB5X^{iRH{v4mBgiGaDXqly$#ajCEb)F zj*bFUzbs;ioEa1^gGVm_L?Lgrv2L4JivFv53;%&PU|^G5=@?kW20if=(?f9cSRC;n zLbiavZLJ#BT71mhCx>z2!W&`3Xr&ClN#|HKzQX3}pyV+0LzDHN+J=W0d#Ifa+IWYc z8Hsm_7!>y6GT_GVG=R&iuwaJSyQvUBd!>*u*>lsiRxx^opzWd^ey#|4#?l3pM``w^ zSAGjMK=jPibS*jF>tKE$xtWV9MT$xnnCj^s-O`M&|vq_GCWCcmhFVy_)?p5f@>+=)A3Svtcs-i9mvw1`=e)_5;y$d%8M zKK#(a9fb=bNe`*#-{vJRfhJ$j%|*Xu>eY_RGz1$H(ZIi^eyq8S6l7Jfy zG{;X(vyfB9`VnF7b5C^k^6`Y(-t}F~vI5jTasP5X>x`KCte=UD+Nc}wy+$0LHwOl4 zjK*M=_W;}C*1vYBa<|reFtgHH34XZ?Z|LZI#yPfCtoh!LCRrVH;nh36$!e*003Gwv z$MPk(B>5mH1}A@g1^VJSu_Hzwi#X|njb(+Z|Nd28S`=(3%V6i{6PF@+8D)}_Nt%rZ zG5I_sbNq6hC2_j3mz_Yq!+E|PalL72Povc^zR1i=Bvm&($YvZfJ)Ulg_X=ecOXDiF z((Gt9cp0wudfHrW_j0#cvyDH=^>Ydo5`g9w`Ld6EGyBH}WijP?Zn;bLd^%VF04x4d z^QvMj>ESXf(;(C^l>xSPdB6=_B6lglsOp0a%})iFIzKZXd-C(~)D5)ggrh9ckXdFo z?MHPGLMMTj>mV1syO$LmcqDicS%5G1y!*)B6<1GJZm_Fl(0C{0_|r~#Lyfy!=daTA70@eT_SKxbEf|w} zUb$b6O_W2wigS==;@s{8BTV|cOA zq=+qAa_Y2G^#)fM{MEu+gSw~lCp*F`^=L<`=B-ztt25!UtA=&Vdlt;-ev2F)ASBRv zM87Y^JxwKhEjQTLj|lJ9 zYkMB;h50YM@=vTfZ~uZJ&P}V6TrX$eKh@-yHz6-CE=kEk{)Vw3M!^yP>6RJ#=Qgln zN$jaM@DFIof)cfu8zYG(C8m36=iuRJHSuO6Mv+CuuNym$Z&Hn|~-?x9=gE}&UI|%=4 zc|a2gJS!Tpw&Dq(Fk{iF&v75iM$J8&dC0}XWUn|T1c{q_g+*fl1`5n96W~wZ0U#S~ zom>;7Oqp%UFfzFvtTZUA-oVedUCKc`LOcp`i(#+-BnGcJ2pF+Nu*wEh9O1DUX@D;> zdc^@mG8`t9WJIWZ3IOBv#deOSDl*@+_JcQZ(&i59|D2GU)<1?{AMyb9e9tca6CeDe zM*k!m|JFPc{O5JfNMkyL|Lb-Bhrj)YrCVdZN6{tnU7@r8d9eR&?AAMk*2H@hX?9a9 zx;_N5E{=a+u=OCSH_`dHO;*snX^OO?hs8^2c zcxqKkOio;uAZz%{dQSg$@i71DvvQ!Nz$ ziDv@BNGl$qMLU+-7&)IE*)3PSms!`x`SpC~w^Ib~HV5$)f;;X_&il7vPtv(4G7|l- zL|tz6n7Xu?ndS>Pj>dmCe(iDFRfv)-V#o!7WG3$a5&3-?LL=^gbayTP-y;Px}iUzJF9oa^GLxGJQ&#B5aak#Ds z>@?iS7T*`@`J0K#Pi#4xHkXTTJ)1h0rF>Glmv>!4-(-EQLt_1% zXY~oZ^c9LXk#^zwXV-4Qm3kN3P;%avVWp~Qif=nrPWIt{xPuwpdeH)akn#G+W6;sW zaEqksjwmNUGuYx-_hGotb!`?s@;ZD|J221?H$t2-CPbZ_q)J^3pyq{)>N(!r*6z$F zYUv6e4RR~;{<91e$f)aHu1>(U(J;3kyDSRXF%%sc2*y28DRpi(IhmH%y*qAXt=;k% zjQmV41d}!4dQnH!ZmIJGQ6|_v5N-tvo zLo#rzoLBSN>_@0_5@mxJLr1n%R;PndwPOyWvz$waNfu8Vz}9aqNiO$WLBw6;nRb23 z0^(W~ZI}FRqP;+#NUBkC=`b3@M1=u{IKK}&U`&CM?VYcd&K|0^w;9eJ0UkbodVzdG zCXTLv`o1T<1uU+(D2SDVeoc{YpJuS?+QXd1(we&UUXykDjn!X)>WHh5gr#?v4bM4~ zb!i*?Ee4C(P&Sc%Z0WH_qvZ|m_MsFfR!8>MGO>S=Mza2CE8p7dZL0o26ugj3TVwxo zBdtux;cDOZ4g~W3uXy}lvB6ge3+kPCi81Ram`4&`6~~fB#>Oq0xKihkF>^p*55ocg zK09Tg!oL^kF>GOPJl6!(kelN$DurQRejZoQDy=oUn8M2RMRpm%%wiZ-Gc7|fNREg| z@4H80%Vc680#XVHS{iNEn9jaJ@oPb~+9(rU$6l;vey@y?2zi(nIf`4!r;evTb zOqICA)r%OCdM$Cl#os#&Vsv}x0^%~`wZ;$SMdXFPzi)m_^}T+cQWbh()_U3QuxiI> zfRh40IIUro36|JASE%T|R;cI!>I({VJYD#}!MT3xR6M`Ci zn>j=RuVhM^1$aa!rU9{4XF!yKs5J&H(v9_BD5cMF0JMr@1w3g~chjgd;3Z#^;K4#r zlM7F(-5o}h1jBgUmRqSL9g*7@vlx99)oNZ%8 z$E=Vt$YWN+z+&jst4crXndVjxSyjfFafMzKq+06brcbyAsKL_{tV4aA!Y=peZ%|-W z1VQ_~EU@2ljE5p~gkLr&DiU&R9)+APgOnog80C=D4fVI` zY350H%S=1s4v%UU#Wy5CI83A=a=4eto{>)U<7iPM^;LTHV=uomR8mPx$AS4Ama^z# zpjT3@o?T>CEh+SLs?-)$$USLEYv&h0R)*8b0c0XG{XHi3JD;CDi(^I&wbtNcAveh_ z%0O}x9B5^?cu7PZxeu>hV91re>tMuSWU^#L9uw6b%?(_8D3H3UnnTR%OvbpjDZRp^ zq%dREqF7>ytK=~0HPYurs0{+Gx-Z?z3KlqjJCGebTeQ%80q`Qv6W&fVo!GV6^zg}Z ziH%n|^jK(DM9?&HaQUQ-?*`m!vtwr6b*R@WzM=Q-y=l}cRl^)0UW9IoJ=qC;0leMx z>PCht#$;yR&s<&pzX15Z*6%#~U(R+%`ZdTw%+QU7X{}~?Q-_{uwK_$q3)k_7*05yR zrxVSN`rs3-5|EjvB=FX%!j$>8*hNW2CYzhL_B!d5C^JUg+#G^?y4FR19?4kBupmNf zjZC?&K1wr@X{p&5rUa}AqWOvJ;H<9#99~`RWRS)_}1 z%#l`a$*@8WMlT=G-}Ubo`i&r?($~x?DerOeCU;pJpp{Fr*$BJ4mI*~30%lRKCZfCH z@Dnzcl8(OsD{%zx&m4{rDTG0rm(k`~qvHTTb+cWgT9HP9{d-#?TZ&t0>%WjQ>66t&57iQxBw@%3dRE;#lTs%~ydnDK}Bp=HC4_ zNro;BpXwlP10H+Kl}V~XkZL^2?)%-oBCi+d&5LVP|6%DzBZ;qWg=Q318*{}g56|r@ z5678h%c(Ufax?icgXzDwFaur9E4(e*7E?G6-So0U-B{2)}e!KQ#z%V$^_b#+WoTe87{1)C9@sP+r(*QlTk73upJ)HQx40v>%Dg-kM^3+IO zP2?T`fO)DS|wrz-p@WZv%Q_AEtpS zk6i1xCd#@kS>jt5U~cPZJ_O^oVVChyJSXO1eIDV$E!Pr4m-RSYs)LQGNTeljkMu@#&J$8 zslZ-0XOgT~RIYO?AUFT;YH#Myhdi0C`6`BunAC^&6n zw`1qOqgPPKKQLGnRBWq3EeTqEnaV$V=`o!bs@n4A2V*(<&rDeDCODn~s?lx29nHeBh4g{9$<}I9YRUl|)MGlT0Q4kF#@+rE= z9I-?x|CeV$+`3OnBt@Ud4(?Uaw18ZN84Dj2M^{d)mlhU!>#g4Q)7yc@IM?jO8I`Dz z{pkFPMK4_U=!aS7G`*(2paHtfBWbN(+~0^J>j%7twbnEF;KOXv)IaEAk_4FF1?DDS zPQHvz0vZ%{dT+U1E3I1r=qJ(b$CZ;c6kHw_``)X^?^)iM-PPLVe@_?zV*jL;WPsTQ zhG~8p00k{!-foD4UY(t3U!pDJA`Lq3Nlg^4l^2vB`BeRNrA3PHY~&rfHP8rL&wYDx zpKFDK`{s<$?ZGUCwE4_S#`Y*EB0^|w$;~%sQLlqvEFdWE;jwweNZ)z(Y)^Xl$`5m> z=o0iCTF%+dm7zjGel?al zZ>b*jF@#3iheltMC&~vhOrcY9b+4pmoC0cIB1lj4x2jG^zsae>*Q+5zgc%KAw!?t1r30xyKz|9jr;SZRSq{FXk=m*fpbth8N?ZRF(3rMdymw07N+AKQX+$ak3SB*W>d3qAQ3t=TUd}&%9>uJQ6SUD{}`n^u|}+ zEjR%th?I1+oR!PuDL}i<6J7OWu1q5>@S36jaXf^op`U;+6-c96Hg{5sSv9RXrBEq{ zNxZc~3sjEID?*T-l@Lr)FRQ<6OW=Mp*-Km740JaJD|*~MI|aBp%sgsT=gBUTci1S0 z$=+V9m_HrAmU?-3E-I0+rY3}fo%^AFsvn9h1HltNxp0{Cql-dzH9;ltVg0NqKc-^q(-maHSN3=UGZ zW0IVfKuVPxoynwGc=&)Ff;xFh?ej3qaW+oUS#<7SGy1^7G7<9l9LA$8MUfS5L}ww& zQ9%UfHLvOKu2|9P2Z`c{@QR~YXzgraXr?iC=YA_GNLs~`msz{*;#%;x&yaGjtCdJE zS*iP5VMyvrjB}_>Tc!l=R!O6btPEJjc1;Y)Bl=^UZp3I41jpnCziwKhIig2? z7oVVgiDO1EWg-(4J(T82thM7XU>(j_|6GkPZPdLS#nclP=;Zydc$qqiW9@O0J+}I= zyldlQ!#?v@qq-TK_YwCguk=)>+jomL{K$Z|JSN*Of70w{Q#t7j)PR$yA3ZyFs6|i! zC;Fq;kxR2WxLL>UlkoW7Ck_Gfj$0|RDfIxWb;N4#f;e@*%K%B-?l_*@t834>U7|5n zuqI{N4=g%YT3486^z(Vg^;v@~>TSLRSkm*T@WEZdT7)@UU)#97U`dCL5ik2hCcYT% zn=QvApMp0zlKf%ZkAJ_CZP+vHGDyV!Q4e6dyqoxS65%ka$9umEf~$7!wLx|1)kk&Zc@Z-!3ccla+ssUeM8f)~;nhd!u=`Q3 zG#%Y)#fi&={4-#~CGJ6HT<06O3xGE|hKNS?zd!p1+w94htB5e@v^7h8oiY|JFfA=~_>>pIG zsL!r1?0Mmb&gx>YHk_*tRZ)EAb2l5*f6HeW@VX2GZzxp`pJK&%shkpbj>J(6{tXm8)^48^hsV&+}(`7)|s26$nq~S z+MtKfK8McROtw$#xCMNXU)T9?Ur#OYkxIdsi~)+;oJ}T=_lMtYxEp76K3{n|J9K6ozzO9_~qFa7P3h^}1boqX2QJ!r>OQQ?lF3<`=TZD>J4I+@}Hy+v5j+cm48?x7XW(xj%rI zgZ3x-pAMz;90S})KGSql%Kq%`OwWqYvfBg~gy+!rG2mjHQ?9%*lSiWPtKcjL!mjTh#|-DdCc6mg#346Ab* zw@zGr);}EJ6P&H<44RprCvC1DmuSML*c#3LnZ!vVmPb3vsgk9Vq(jgg-!mIVcH-L} zd!$Wlspmud7WrR{_rCyt=b7(2so;bD=?!`hp(=I|%%I_IgITTLRZSowFvJ_*RV|I{ zr5t8Vqg&}@KK}bsN=DKO>7@nbHSy)raS)8BDzsV)#K?Nf#K@wt&A%`YErnb+_u-Lf zI=oUiv5*1g9d5z1tt+(Q=hp(2-@V|M>I%Sj$48P+3sAkl1UNKUtfMfYBnGU>yzpyE zv+7n82i`P|(S>8BL_0M${=e+_(u&x={MO2WrHp^C8UzxcJEBLFDo}1VbXOF{i|ajq z+PmmlZj^aA8{}cpsgq8Xt2Xdu8s4-SSsrhd_3M@Be>Z|#oi!8;P}S$@^P?BPw({Kz zO}I=P$zdE6Q8qTfBH!4w3zJhV#qg(2`G6aXS__wk)4EY?Qo_};UzlSQl30uoG=%B* z>HF@Xo6E3a3hjt#jp`^>At~ZWp89cwG#+EFL6`cdgExcUtAT|@-xT}N!aE-y+-0aw z#X!D!wezOF19y9Q;T)lekcL6f-1DH3{%EQ_RRV{w+vhZsVD6w7X*U6_v-3GNy*~yG zwSnif_Ooh#Pq}tEZ*@suPFv5T=3|9mt*NARttqxLt|DIPPq{c>LLG@r6|RqY%a$0I z+QEgr{Lzj?L~6(9xu%R#Gy0+B^d(GRHShw$`WU(4%(=LY@|w&?aKz@&7>EzHgcoZ1 zClSGCgNMO1>&umNcV9}X8hx12Sp~>(O=fDu=q$|-JyOq)ov1A}5LQuOZMhOEzu#dP z+RvBP0p(+(GkylE4@Q=#9j>%|rKJ2L%?EQiozvXmH^vY>Mx2&9oPESwYgK5SVu$QC zmOpEW8h$&ouL+}6zZyZF*5&w@G`(ev8l9_w1y~c%56+X|N+QzO+t(@)1 zN5Ka@RH@X-K0>P~Qp_lsCouN~u| zh7eL!?BBEOb`$d}2+`-i73ry<(a!bIcD^2+lPc%g}^QrESBK<}$~lN@VV z5m3Gdj^!)#1w;`zq%Ndb@6iw2A0|R>EY6`E)=H-_vm+dc8xyeCMj_}KZNj(>x47)- z@AA`T!936W@QhDqkYtcNCW~Cfar!DTtxM*b(L57Tw{kn68sWvHrS&LQp0jvQRru<< z6vu^n>Sz<=AcdBUl*Kw`IUJ|cI8xY}7O%n25{z@$$tyC;mKPb@95(SkQnrx<$@t~HUy^Frq7rKqQ&}vX z&&gO$^V<0xFGJlPSp$BY)4(k@|pccxS9}i~>P1 z0^J_~OX1NVU#b$^D_NYqECy_06=M?F@xCGJR^AHe4-=D^m(>cb%1M^5Zh~b+Bp`+x zMCb*w=&~$K`1G)glm?Y?hwxltj}ymc=qB3KukqB~@;dwG8r^6njD=-qaH`;4Yuxm( zAt-V=vDb#lh2D1cTTgJ2pQ?a=w8dr`EJP4~E{SJ|sj6K0_B%(;eMD++=D2j#S(Umd zhYMEEtB}o%2xBg~r8Yr;koF-(qu*eW`ii#El)D2-Imt7^D3+6CQgIVb_hiA|`kNl_ z`g6s=NvDqqbIB2cN35AqvCO+X04aq3WiaU@mqQk+?{?!(`mrAMSUR zQjyf^L_<%3ePts-3{gy&S#DIbieCWSFq>CvieRfd`A{&WZj56CW7RWa0MuP2hf$Wu z=T{mRnxz_i*x|xvUu#H|o2d8A_QLDS{h&+aAw$qMRqx}zVxLx63Auz@*;q|7<}C{rlTvfFnk@yOz3Er8{Jp2+RSbD%aZV zc5gC^KzFC)ox=8G34m8Du#e1N&(9#W5}7z&ijjg+k|v#*RBEfPOeg?Cle6*GAw5)C zD6IT_8$*a9WyQAuqG(UIFqnPq=;b+`k8OOF+;D&Q^-Lv5==e z%ssO-qOqUw!8TR&hEVe&gXa(&ti$CN6Is{!FB6xlA$RA+6hG8Tt@ps4cNfU~o|jV! z3gaiyfv1=>eBQR9iX{11@DXwFCX=}g-{f#EiW%fL?dEiWIdMc`U*fG(f?LW6e=RG3zF}lfkt6xOxrMcN3@WRZ3(Iq2L1Jpe~ z;;DAxUfj2G6V|}j*tZ1ldF}oe2o`QxNz#T?*cpPxF9eiG z(E+LTPYc6X0j5t2SPPXN#=*?>CkNcjCkG8_^)12utgxy$N7Q6lKp+|GB$kd&tvMIJ z>6ZjkeI(>sS8I{Nz7b`6N?SHAwjf4 zd)B+IR%QH#aOHaZ_1sxxiq%JCUUVL=x2a)=YvD&mj9{dM{4yt9D69wmeaviI=5L@` zQt_--uIu3=39|Y%QI$O_lU2`RT@v~3F84OZFQ7>4hed4rtFHD>+ znxH_DZk{iCCmk+ej4!eIh4SBci1E=n_IrZX+VHM-@?yhWh=`0&ujddmSI&XN$RzL!+je8Z^}xe={E zTHQfHnr>}MN)91?B0l?X7T;p7Z(%f)GY$P>?%NK6#Tj*vn0CMYzUp32b(-ix!&_(6 zMA+-NSrx1|Yn>{ii_E|aWCvsW;T0iqILVG6*G5sLsXncH)YL|qF9vhL?Bp?&&(D%j zNpCOy^^Mm|1@%IzI01(c0C1beZ+8q+MsJ?x0)JTX+N=YG4INU z5T)YNaRq{4;>tO&HarX9Ngk9$O1wnukg5YAO)wnC}X0#YP9Sxe8&-Kl;f z`GK4~a^nXALBpcIU765hGP`R`BHFz8@8>D_AG1hLwwGw?$6Q_9fl-cJQpZ@-y=u7f zV+&!T4x|j%BJaQGI2{3Zg?iGZi(72|#T7$V zRfFu+J`Wc(4K`!G z2O@x`wbL)=Ck7v*bh?cv+2p9s1H!j59%Orgpsr2JnV4BHL&GB`qzU`BPkX98U0zAC z0wXIP*+VjK%SXhFOv_`;=x8pg%*Dl6i`m5rdufHYb_;1$CcrH5g3D6k5`)i`QM=Iy zlA`5e={4ToDrE8o`tT5QBHBr+*%}BOA8Hr$syEW}YB1OH>M$u#=>{}?mUYJ6WtHL% z*^3^83kmdWI61|;tAekuE*=Yrl#qylKv zTCOl5{c<769}hBs?Y6kHiVTQ`LJ(dbN#hX`4CCIZmIpI0jV`B;ZH#enC(!s}>{NJJ z`^UXm`qxyPY_HFFFPkr+FL^x~1beu)86Fv)op#bghA93}lukUmHSWcJ%rlpypd?Ne z;BZrHE%~V><_Wtq&bU%_@bp%h;`vtDH_NG&_Iv51b;B3!&GE8KrQr>JGw%DH;Gk^? zcmSS)VN(ix0RL8-^7LjPI})(4RppK+9d@&f;J5pxX`OT?ym)e)0r}gO`O?T;n?w_< zrtS-;(FNleC?-eg`b2X}3d#yI{p7J)p$>aiAGeowySb*Aej<#Yxo+Z(HLgV?s{S@y zO##6pTg2NfklGY$1Oa@xISsZvf<9Edn!?6$n2FBS46)^|5n!AO4F9PK7nej06ZN33 z{78~jK>={z=kmIf9i|O$udM_1+TgJ0*8RXGDkry>f98R#V}{P!s-90Q4Tzp~7Sq7r z#z#~a5C-2m-1RD0Vgy8wpOC#6lN9+zwbxQvU5vwMrS>UjcK%9W!2IBbsmArXRLuOo zHKz_LWwmmEM%2>*tJ=cT!TR)r?e*aDN*HQgAJw($MekcwUU*sF5^j+#i;Z9d=GHtq zGmLwpk5F(xSv6~1%0La#6i0`dCC234-0tz~q5vVk8>*Ae*w+})ipxH|+GG`K^spuydO6Bsb--sH5v{Kry^MuAp4a%ITQdGu zG~j9TP4^L>6ve%hfpj_Z&C4gTci^2B+8?ZX%0$*Bj?c6kKNq@LqQxxWw~sS>c`E)A zAmvKG@}hE+(0&mc-Q)=6r+xQRCB~(-Q50OwUJU1~=JQoRjh2CWgZ<71zPgKD#4O*Z zx9*t0?dQZM&&O`_+Fw$7Yju^CV+#id6_p94mf>N>aUk|O0%LwkDdaXB%&*_VxzNXj z0@Y0Ex=-yGKk115t-CCs9HjISN~?7rME!EsJt=F}ErCkMF6sX^fwc9}cWHNz21N;# z4RtX%oeSOaFrk+0P`o?nnte*YRndb_evvq0FZiXyxop%37I{bbZ1&)s7y2KXt#c*L2*qci0$JWzRkL%fH#KPkpSh z)9-WEYk>s14#q(6AIkJSykdT>QNHR+p8(a+Nf5O?fX+e>WtO?P&|Py&j-c2=gAa&ym|k%_;( zdiRpIz!sF-^TIg}-c+Apk0)0d=rG+R-mrf?u|R?%sT}U8Kn0Dvo4KcjgbL?x zXpnY>7Y;3my3h4ry8EG5?m7zSdhY12*3|b8f-qO2>lIv1(a)Zit(@1IW`(bEqjB4` zqV%e~EMixxD_SmNb55KVE<3)9{XPAQ{U|0?#VIm|;zk%*^ypCh)v4tZbP3Hki+YUV zs9yM?)SI&Vvg|sJTK{&t_PJTV)>)AJ=2~_-Zs|(F!M`w4YH)O|aW?G7N-=!1iqIWf zs6LPK>rl8V-XZSG3f*=~#P+bvcNs50$7m=1W6zwN1buHCa&Q0NqU4Ll z^bTWQm41Geytt%tuY3&R^HiGFVEwu!DBF@~mX7du^tiVEfR{wzSONAKBpXcZP@D z)n-u&j-#&rdvq`{9&7khFG{8A2cO$gs1aj%zMuMIQQQyO>~Hvv-B$W|i&48>p?6Qj zy?gm$A}12QNWFVZksw9SKc9u$rxeH@(74%zs5V$PgOSirw`MrhV3X4a3 zZBAaW`VHORqPb8(%EV!cH@>?seI(Ps5l97?mE z5>;yFJO?L*(b6AwWX#qGd=Gqw=KinIs+J_ur1hHa&@A>+TIBC?LuE-@qDtcgjX zXw@a3n1_U(5oeD@rxl}yrCS|&-!Lqy&^^AtMYf`cNFLw%6-h;!ZkX&;q=uF>bo@g_ zt)^{9w%&9N|2Q0P_>CpTRD;P%!-YK`m^IP%i|MJJ+lD93L7e@RFVz-!Hq-rob^&~( zs;H>q#`R*06Ru`c6)i-EhW?AHH5DYPo9)50wvG__*PFXE{Y<9QRPcUVmjaGOK&|xg z$4YC51N8>(^^mnU>X?KMzFu~pY$(Mk{*RYU)^AEjc`@NdbZkZS-y4Alm3`+gvC=Hd zpBmM~8HdL9SVM=4*@%A{Sh+YYnk^dp-!A9kNj)7MmL%wvc#aEyTWCN-IDmSDmP*t5 zU=AceVRfOA#ES^bW{4&D(X0D!7YD?amfqJjjD=;>CBnO_DuQ`0U&V8#8&jEfvjOjt z5WdOaz?SW$>nz%ir=R2Km&C}^(`h5|fE69(QP(s0%}xOTRqE$!pLRe2gp4BG8vx>S zQ3O7#UW>gHi4Vd{N(cg=5qG9k1ZF$5$hz|Xvc$l_&Mbkt#jS8p!-&&U$%qa6(`0b{ zpA2hyM9nfrar>+53im+b;*9!*_YFFF|iuq*5Z*?BjZPHYO zuD_5ndY}{{1`WE;31ZH^#s*=!cLbaj%wVsnQ#Y@A2)B|Cb9+BoCP&UmktEY2$&$m9 z3=eHy>g##e^9YZ4H#UGA93!*T3;T!43_Erk*1GXH;BzC2w;A5kh1`LNXyvyrLrZpk zBz-pYWhm0;_X#`lms{sUr<`8_!# z=DuQ`38Tra_DCOW9cYP5Nx<^IIt!a4>7Xn(l`i=njNO$i88~6q^r~(?Qp3NHje4-j@3#T90 z*r{|d5(OAGCK|XKiGwKu9Aqz)@G`%0B)04&Avb47|E(EVPnd9H{Mta->|+gBjF@MK zF~Fu-+gaprEuNGBts{6j23_%stUj<7QFqS;OqzblC`y3t;yGFPjO>qUCs#XqL1oiH~>#RtdNNhEw;oWNe0Pzg{95$gO z1r2|2)(P}zs>kg^b~_Oof_QGtbOru&1@V0u2*WjA`Aq77_=E)KLUvH^wK4A2K4MFo zBzIKYvE7${Te0I~$N}vrFdbig%FLSGe8uJ&>&!Nqraa>OP-V1R*&3`SXF;TMh#}D! z!9PBhU$7MH50zn9R;+0^^#KfC(S%SVgI!&UkwGgW1-^jmuzG3cTuNN!e(TD5t6#!j zddnNmi~8W~ai}vMM(_B7)c%@*N3k*}xkOAoVMmZfU_E@`237`a&*X0NDbZO)63A$~ zG%gWZK?{2?P{#BF$a8nTvY$lz+mKGrB~Bhm*j|q)FPP)AW~!_G!C5DrhqX2T%jxBk z+<_B+;Yqo9zx8{LLxt-0hsg8$u$gC!K(*8P&UPhUV4){O=KDe1M7pos8(tF){^*0^ zUhFnv{iwfO?ewofUgG4s(0wW8<`{m0eIIyoweaH_NG9sKU~KoX=Gy>GOalf%E=AeC zEt4qtnm^Q$|7hJyVNk7OFKwyn)Azw~epi8RSw6*lZ+@5_`ss0*!T}#!efjO9n+hR| zQH!U=c7JAiiHlAhFR6YIVN2cUp%nKe(EEXTiEfly^bZ+5s8Sohvmu<71Dk8c;RVIm zXKOtVqq>A#pOq#EGjeO|*%=;+$$D!gI$<69=RHt5(zKv(BHRLJQWl>RV_gg1pK=p3 zVqc8h@ZmJXtbFK{Y-Wd|aU0ji{`H37v=LqSH`7qt)_V`ud43vnB@$UX8Bs`Ui!dlJ z>r%F;g}Mg|z(kTI0J^+>mS%oxW*0=0aAmJ*=3C$&uAJrks!IfL94=i%Bq^{D^{6bz z1f0T8PWN4V-rBj@jLa9bs#}L){$j08WuS}--$A?51+Y(qomW~AYspO1yL{q z8nlTL$Yq`RlqbJ$<1U~^Tt9MSwbU=A08ZDd= zMTr#Yo!n=(zb>Xjc~zaMmT1lpU-YQ^MS)AxQHIOA*4ap!Nm2q}WC>ou^kj#3G#Vnx zMy9rw2dfm6BliM%f*zQYS|`AOD@RS>KMmWPnFsWtYGA-rec_%)(yCl!+k4H$0DEmm zY*-$gJRO-X7PtvJXa*Ld8i);+P?$olhyZPiBs{PGy^Ur z(bVbG@JHOF6Yz4;l2qd}FD5htjESwN*sLoDbfoPlN>dcuz2Fjavk~z7w6w`|ynD}d zW4aIGnUwtQ0Ic{jSnKuYelSLR87?vQjTwRu5}EdVR+C+Yyi$QP$VB0}h>eaAZW4gR zEi!xsD}|y9Y@^@)BeuWx?vl)|UxdZOldnT$-qE_$1ZUhbOKw>?-spJOTy{e2ulaQm%Q^o;GesV&+O5v<~eZig0-Px=v2j8zkFD}m7GCL zbF%~&2u;OYT384Au)vk(ZNUPUS>w>f)V(C5fH(@WSIvxy1M`EXch@I;fbI-(pWhKV zf%Bjz%<6SxYJCyT%IO<#M9p>l-QUIi@ZTF)i0^n!6br_muv&0Sh;~g>XkPEaJBO01 zG<>Ve7(GzL7i$WO-zDYlf>F8-tdyDfqK}%qJ(qW2;pJ!l1T(tex))6<6|SM5DKfa(g`$plmDr$KWG&b>m zT@xK#c3oUZ5{^XCQ`>;ERk(@F>mU27QSpzm9}&v94)Hg%RVCDnI4iiEK7BaJ!B1^{ z{eHeLLx}??gsoB;{cttzde?;GKB9@CBlr_ZGfW+bqZ&~nK*BJe_;vuk?#X)<=|^Y- zu3cZeWW|9IhB5&mVyU<<>ukZTrm)z%&>Gs3TRP#8hEI-%`Aa1O)j<=1EFQZ3jB^~v zo5&gE#WT-Zx1-ke@K(tGCxybGe}MRRPrR)!*6j?i63Pi#5poJl9=9L2Zg_5aKR+PX z&Ef7Z5e*X#h;!g362iTkD-$Ju3`D&9^-jZ`CUG`YOC`kf?3ogTCd5CKI{yn`VGB!y zw>68JnMEWKV1<;EriZJiSWdR5(enJ`#msNE8wN`fSXy#^?TXSC=bGZhpdK2Kld?Zs zxARCHJI_SC@-Qez8D{LK#k1d&>;Hz0qn-H?!mK0Z?x6j>clX0W-Uw)J?;V#7lGo=D zu>udQZ*?INWjaB>&Ymm_ znH1ncG$AyuPrQ_rN#DAJ|uMCvv80Ub$F=-EWvR&(j}1D25wc;`87nAlzwGM6~qOuQ%RaX%jtVNL@W=LhL$ zhSfGH$dA?Ulq##Zblxg%+N`;-J6U+$DmHqS99Mcr^b=#>3zB+AW@`QQEF6Pcf7>nqs1fcE`rUHF@PTl!z)MkWpFzRv2EM#b+~PJa=?1 zwPZXGkJ!^KYJtKry^3~3|3zQUqNO;)BEiBW9|k&!T)xXX+MRGh(gs>)=T%cmjcrI_ zI0C85OL(VAchlq$kQf+m~h4;*w;D=yQ!u$lP z5QSK5;xBA&CR;S$^URXI;=Kb?n0Dv1P27xnqkE zM>E|t$qE0H(d9;SNFH^XD17#mGow~RCA640JzPLBOX>FIFjbDzOox@c-y?hTJxenG z1{Kdugf}!v78~7IOtGygbhL}BlJ8M-t3}!-1upl!QeaxE{dcj5j{v`wHlB|`U!&z1 zHZ*TBt{5<+Uls-ZU5td_$cfFoP_cNDLX&`Lk7n-=bZMQ)Cy0`c8yxhOoAn3aD7@|7 z`6h%u=H<`ec07tS`8)8*%k-pGXCZcL!QmAfy;d{cs8SqmUa&<&*+J z1b5{pJ`~%-5-XLOaosJOWScc&`BC_uQcUUCd6)mW%Z-_2{@)VS3i-Duvhw<+z1JP* z<@0$K0!j^>zn)JcJd#aK`H9)FDr922eF2sJtOivDKA|$v|CfS_-{ULZneE==!&w+Q zZPC0Ya|h3vw(6_mT&p?1j-p=s zp!iu!#Ig;l@scilzT~-ez&dSY&ajK;kqk?0$66>|%Qey@*6a)_Lz1q1Uo+A{$V@l1 zd`>YmkgUanyZ4v?uG&F#*eAd)LYgkZn%*I+*_jkTziW}tds{%stFV}jI^s?ujfTb| z@940*ux$dg$j;R*<-*0YTPF<;X$_Jj_V+xUt4jk;)~@dSFH8xM znG|dy;Q=erg@5-TL&LELfElt|lcE#I!C)$7Ay1n>!1KER6yD(joTwyTd;KRqy9Fl2 zjVl9BAXP)vVn-dOoE2&hDGpm3NiZ>R6>kF_s}B$l;?{;br0d zO-uU6>xWr0UyHTjO&$o?fy5s|mI#2kOopg2vRPt*4gFUT*EcTBm0;c&06k{+xw15b zH1ImgEp>o@4zk_=c1=9>-zht~=e~kDZ>O}vm1f{JvqEwwJa=ZDVn^lt{)CSWB&&xQ z&)Np=jAFkM%4aL;MJL|@@14=w+?3tl5!OqM!-UYoiJ@gcYVs_SG{oo1rNQc7|6so%5{8*YWc9YH-z?X(Ec{@ z>?V_yWHfJeID`H-qU*DUV3Esv)PE03dl9G!|gJ%hpLQ!~;$b7&2JXV(RA)G827k-DG0_zIVvf$rN} zchhjbo+~lzrK1KzNsd0#lAPjJ54!Jm-pYO46`xvF5T(8ob}(o2@u^OhFAz)zj1e({ z$dT?22x1Rr(cL`1u#3c)RPvynQbwZ-VL`I^j<=WbA|%LVi-hHsrbY-b40X#b^suz| z%{{z6ahRUuH2S-(q7BoeY`y0g8Lj`AxuS=dN2WM>-|GQMRfWw+ZxD2+k+# zxiT}2wPJ)UVP{SE`4*Xce5nP;hm7`D7$MWti6qLt&&3TCHA7I@IrmU;<+AM%w^s3C zzqRI~<*eSWL2LWeVGpdPu6eBK+wJ2HdR9bRPj1*GweQo6YUz*LkxZ8sCpo*Ft;_CO zo@D{N@I%`xi1DY~duyYE*e49Q#|#p6s5NcMT6O+?&sQFrh?(G_f*zdi7X<53;6ydF zD$5gjBDNqLAV+V*{9CS@!@X0ClTl1S*>Td}4%0qF)l{>J?KWQ(HZwcFXXFn4d-3$F z0JdMAW5_##-w1BE+88utIwg8vhc12pUFLBD8xbt-fu|#C|B;J`7mG9)0d}7$cWq5A z##GZSdfZfl3DYTFX;1epKu!QBu&kdKkmY>G{@*GWvKZt%^`FjVaD1_E|K>|)01(Br zBEV38KF?8xZ$FiH9p{LSzQ83)eCID;^pCSlf2|YA|GgQ?2cFRNQ87ph|j}EZb6*cMM zDIa|4>*&G|YYq;$n5F((=}KxGEDx#VGGOQMRB!u#SZj+}9bx3yz4vV#;L$#JFzPX5cR@^>m=!2|NCZ9cRI3d0T3~s7P6Y(4@bzcor zkCR#Oq)IRnequ9=OR^=SC-b0liL~*A43Gyg0hT3)FGMzEJp_u-jP+4}a!cvMbdO~J zA+5&#WjfLCKypdH7ttul9&FeezR@2ZzH+0Qq=#5Ev%E-R;uNs$Oe={BsBLGo!i@$8 z+G``@j3#F#4?ex8WjzE@4PZ7-!a3v@VrJSCMpiNSnFJ)bOIh7G%m0&Q@ms+hf*c;n z1Rtrgljmz@X#stNQG4bi);@1(BYDZqL>^Ji1AGr=C;?&u2!_TT-6q{D54A7Z$UvHZzrqfBR<>740F!tFMPygm(N z{gPej4u7}uazn+b@C5QhNEHQZ4l4MORlsvh_S{zAs=TqIRbf_SAt!&NuVo4(M#)@y zv*^?4(|_o=#fh-Vmba{tl$LAsJ!U=38A zx$@@v&u%hDMNgGtXvb)BE@M0IP-)|Zv0?<^L|ol@=OmvscH7-Y{&`Iu50wF4+L#nQo8UPfIRBG)N_;syAc} zI*FZTwp`=}@^hqlvXJDsuQD!TdU$ePzp#-N;GIqHZm&nU`A&_pD`c8P4vVt=GsKFH zwUW$^x!*97hzz!?*&GzmJa*I!=`CYy7 zP0zwd8Fc{*ODpr$=J3l2z>#UB9EC5A%&?I1#L|`{TA;>4Q#vF!oeQx#p~`+T77XtB z_`nLAOdOwwK#@cqa2a0liP+7HOK78CpRi*BUp-z+PBkQ3To*~_vF1$7{p)w@mdV`9 zjHT(a(EwTKYyufKaaW?^@S~C9V)U0}EP@*;eK-Mml!&;U$OQf)9XbgsQfi(kJR%Ig z%J;+dvyF+c303zj5houJ5Bn|- z|4r6xVR-wO#9TM%Of*OLmaZG;2R&^9nG*S3U+ZO6wl0?{Ph2xjj(KOj zmn;6dyLohE@ZFJJ9p zC3R(=wa~qbI}+F;2ZlgXHigCtETQ=s#SL*_o-hY$K_pKB6Qr}B~>ZKRXlTSoZ@Ab<~O4tRD^=k2fz@I^gWpXIn>VVM*-NW7Mra(oemt+|1h6AvVJ4(S%JcZ$E)eis5*02>4g&$?7}tW$ROxtIqa<_l7Nzs!_tT?PHW-+|trx1wO)c4)O!2C|KUVQ2q8qB+gL(xI{_AzDbi5agnK&ho7#JLpo$X zb?iu3lu8iXjI&f|3lv^Xq=CYbZ4oAU(O{-@J2y=vP>-kifM=2r%zoLNEK7`5z$*Dr zUkw=5Oi^`nR$hJdiNTEDdnAng7Vn3l5uojDzq8+SEn`>|1d~+E*7VVoPJSp}&d|jd z9y;=ijh5CpwvB$zsb^t1QS}~KbEIhzur_q#qt?io3y_fFk@<1h?e!-@+$>EZ_8Ma~ z)D~)CVz;o3uJE858?>2f1&z1fZ zgw2;t)D(K#{vxQ{-L!n>kbz&OEcVY*K6 zZwGxg?K;rh7U~m)nyFp5&m_9^a`ae4L(u5$k>aWobZ7}XW{A~Ng3(lh5x76CmtiG5 znh81%z?&1L8ZHP&U{)%4Cy31G_TXePUiNOQln291xQl-SLf$Hk1BZ?x)6x?M zg?#no%aeqwg8TPY3zy2rkx^eaOrxd^aeuxFmzxcLvY5 zaQETIy%Tv7x_7xVFNK82}caJR(cra2asQ-6h8 zZh6h5Dw>KE>zN7Li%(XPGn{{GvE_CyL#*$2^|TlVfVY!Vucb#OS6+b8a=Zsg5kII= zx9;^OL#jAJR2#pSANM!r(p=mx0nt|s&n?=|)bp`4mN7Ww0IWmhcDMaU!P;lJ+1 zGSkiVy8aRy^L7x*0#+UKxU)O)JKI0ieVXm%%}kN8}X* z=Kv&g^jK%4A+ZIPuT2m&M5zJGkf7otdh$9m5lOjk>-T8Jb>n%;Fw-1E{e-eqpCM&C zMb939xF`a~3MF)?5Bm{hd0GoZk^yD{f|EpuZ|U$pdHEs^ZAAh7tMU>JOBkf(J46^S zwAa!~{^n+zbT4naJ5e?P!q3Z9HkAf<7sWdilK29u9J1#(RTUOY&(na)s9*LIWZS*2 zC8EBh^X=!RoCXGPUK0KO+cWHaqYMC8r2_!z^pczB7{-D1sNy`Ii34Im40M)WW!X76 z6-14=m2Kgo>naNlVu0qZ8wWZ$N1BLTd(2#N%zlQ8V+KiDJ4tT!^$4}R>m8D3JL&_I zyZ+(3FRF!o(MAzEG3&*m0byDID6_w5h;h<>Vy=BnR*uYXSZ03I3JuoDpWr-B zGI)=?D|U6xZu%Nc@rL}S#Ce{?__>CGyiNM;5(la)hDwAmj3x83jm5?8pQeztWBdq& z;nhQK+4nI}j;R}tG*He<#?V0B^}N5!nN1&(^y1Y4u%bdHM59_+oz12K6?CY#>RoH9 zw@qU$c06!MF7jP6G2nM1_^`)lUvo95QOMgEOn3{ZALb!JAY=4EoeU?Mh(=`MdnDi{L z$ExF7%=hNd+i2{`iVDvQ<$0$fR_y3d2#hm5*Y z_Zzm>1kZ4Gf7{_1xGeGt|E34oc(H@{fD3%UDI<7qEV% zN97bIqll>cE~1e;=W%>)3%Z~r-P7|s(9FN^SpO|zD(H8es+Zh{+wWsYZ0|EDpH}b! zZzp{H@z;}aC~1`kh_dNA1Y(JewT&y32mi9=hJ`W;`Eva{!)5I@@QbJTa`>D{LA8eS zgZCZo>bv-Z{iv&Xk$PGntW__L&wa{yez9;IIec^9s7rl05u9|32AkWk(NP76e~MF> zT70exJ#r;nEa(gj!#Lx2gWc9Fanz7a<|R>K&>;T&Xi#?Iiq)+yuWVvKC-hI4M+Tx( z~=wH~%9P`%wL9X_0m2$f$z$yHCd92pomMP2^^6jN;{Gcp!M1dc}v0PkdU zYzEqR6cjI?eiRa$vL>rjd5Gc&Ttc*oaJKHWt6pm9`l#hW94R|wDOG^kvI816mJEEk zP)I6MQouuNxb7a3an7{sLc;SqhkE>{W7Ts85NSXsi!-*JruX{wbRjA{O~hWV5;!jK z^9;k~U5jiu|C8p&0E|!ZpnZ5y&ZD53{5G=G&8VQ?0>GmK?Msgprv0WXSvdJ`e0Rf3 zWdJF6K#hQkV=sO?9fuwRM2b{l&0^oO~s4;c$|JSr-U0fpduB9?#m zxyUUIz@CU7a zqddV4f88^|@83$K1w6vtZl>*z5gzT%So15rblm zH#Y`brmm^R#CBt6l78l}cIn4oNUn-4&Vu=j7|=B)>v4RtR6ujLbF(M7a0Nwl`Z~9V zi*XoTaMU@SifJQt)Z%U37{)l00834#E>D2|`Xnpg&voh!^k~m?_+WG&zvYxBMwMMb zi=_a#Ush0sZ;Jm~c{UO(=b{yy5BySy-27tz8>#*dfPW@Mbm zh92C_I|j#;6_QNZQY#OA&+k~*c>(`~6;wvH?NoJsj|^RuQ>4*RG@Ctc4z#}G@=dsG zYMY5ObflEef4i4w`X%$H9h0sbX}l3uyJj?!dS}qkUb({dJ$DMi;6;P4*Sxnb@%F&V zk0*&7q8HJRLni0OJZs7SJ1MQ7&izN(+Kg(e&ja3cke1*_GWo3we2z^<9C()~Q=9W{ z47XM>dCplctRYVj=TIaLIfxi+N~K`;Q{*LG&AZE%WQ>q`n2}zOR(|BC4SJ`t2X!|Z z<;_{)b_+Vm^wS`Q<2!}7lkaPAcNB1=u{~di&gw(Yg^81N0bg3p>XWIQPE*cS*oJoc z8lE6AFLHLLg~}vTBq?MYf2<>S&iFs!izoG>kk5SZN9j+J;hGH?IxF&T6-y&iUc%2z zy8QD?REUQM_Y8Qj_@RSStV2uh^DEu)1$h|(N(=1C>9H;zgW^@B9cKW$IV052k{GHG z@GL^|ajmP-oo0g4)Q<~C_c0yDm^7pYvrdm?lsMKKubP0cIK)&A+EUhBiE^#Brz}Mn zCSsQadodmrNw8s#c$S;MKgu-g0n#UxlTAyk#EBZZ_9NP4m{ zv@TzKW_%^SYHE8cd!ibc(!bA<8(s6`r!<(__6*_GzdVAHv{PJ+6&JGjZ<%+Y0syV? z+bfMpPauN;fdsPC%uU{>gsL3Oc%#t}Ej>!@Hjutny!Ry5to==ircbqjI@3h5AG-e@ zC*pa+KPOD(Z|q4#$g9Qq@}Ye0;`v$u3M^a6HFVVzjK)}&?T~+9y*&d}MDhIrh%}*? z9y3cqv)8-ywh}$NUxgPWNqzLeOL8G%xin7=?jMw%Z74`Kg!@+R@D=V#x*i|T>cY`E7kcb`rfZvyAvPawd|-2V}2 z&L$3?6q7vq%$XV!IzWxVaD)ik3lQOq$UzkSR+2bfVqvaAT1inV9Ldq6w%F(IZNf4* zo~wAs8B<#hqnC4#Kg`rPtB)D480lRO@7*U~%rM=+*YMkyq ze!(Zrfm7b^eZD(e28lVjed)x|w{%Bv?Y>@*wWakeX3cbct2}bx(Ba;LnbzNx!ar4Ou;;!Gb&5H*di`OCS$1DN~$htn=AI- zSgeKm6eu#Gr{>jv!^b#(m-_$c5p+8K*QIs#+kb%xzQ_TfgZ~`lRXofsODIB%n_<4$ z2!Q=lM50K3F#2UetcoZ`TXzU(JcM(zKo59|bW1!#m`~hP7r64~Z%nwL94+Sbj4KF8 z&jX-kno%Pe5Zjs|8|5P*J?Y24)o`i+|DV;{z3+yOY`c@Y`(+(@Nlu`q+SAn zXSUwoP7H@vZSUQc2<{L7t{b+y10(#|VVUqWmuQ@ggh}Zk&pfa_?iZ^PO=JgQN|j;Z z^*xe1&^il3LE>1&5&-;~Rk%WJ=5IU#-1S=W?l?9y0DwL4MFN=4GBwuSk2~Ly^LKJKQok!s!a8OkpsRx`l`jIQ@cd@fOnBV1>hbqeCc9lF z@8+l2slyZXc4ENxTbV#sE{rW=%ehVzu~3 z=Hy`c=(8UO9+9S0kkaAGO9RNi*0kmr5ieDaZTVC5pszf+ z;e%f6t9g1)5NHaN&kvJd(M9}&&y8>UX0hS&rx#O+V6*N3DamvA%@%e_*Pgc}g|qc^ z%oOYBKF#rB-IQ!5)dYXdm_6SW2E~WO?m{_%{%}_tnPr3w%caBFA6x7cJ0u{uW3Hj1 zn>2but6aj#O>?^a8#itq&s(GZJg2$ei=&JU18W(gaM9CIDPu~i# zYmGWRT+|O^ye)#O>|li*yuQ#B1v-$!_CCI~2@hR|{FiEDg4F7Mo)eD^!Uaj%zosV5 zzLLR1573XE8DZ98w=s0JGeE~!ZLft|Dr#g*W5G+|z6{sypB`NoSFoEIHw_8v7$&_? z9nSp?+6qQ_FGd>9U}%N)7wqT@)g!?0M}Q?_0l{56(%n-phhr_cnHM2;42&FuP-@LK zo?t9a+@OfJdxO%sOvH9TB&oW09puo&U|G$}YP46Vtk$=4@1~v0sY2t*ARos?DY9YT zRAj#hh84J;hI6&leQAcyChNG$iy*rLv5?@}t+;qO_rDRNqp+Z&pSIM@xV+nM zGTSwsg+rmNdl9`pGb^G}aPBNm-+5^qb^l=J0X8{SWx4ii=_C39|JeA7SrvI|zsTRYfkB>p%$$k0?&3<*47q_NK1Ic`w$qC;VLn`R(`MBd-c#={YTt^%6KJbN?(--k#TtTNFJ1t_vdg zG1Q#->|V^itg4Jr*SG2amA~x&%U`FyJ1>K`&u{sQECD9_=O;7FAe6MWgvYnh(-%t${YZP0oD?e)t4qq|u*HwWy^`_y#E?0p}|n*4)oo z1GE@079CI;^)r6v67Jlw@1LD=76k}`%?0jUja63x&#>~MiSXHGr4Z7qrBpXoA(no z1Hn`8fG`DwFiNcNEDr9fZ@AFS?OZKrf`KNRox3N0w6#lKjfaTXY^@SxlynQ65%tVledzL37r1YGC4xgZzy;t>VTS{M%}Lj5 zqc}ZE!i*%c5=wU~muF1_h~8$9ifdv@li;e^PDPGHs0dx3b+G$?H`CQD6vV*eO&8SU z0Q2xQprOy9%e4aJu&NM{n@t`T#7%`(SK!|u@D7#9nZAbe$V&mVZc(-V-kdrQ*_;kC z;rCXq=;%)QQ~qzZ@@5ziulbLzAPZbNKUTiHl`>{b(Zx>oZX$r*k{J{DybFZh?*6$C z84y9a1ZA{9MTrUGH}4hY%^W>!k2$7?S6HZe_|5(L%;A}XVyW0!cP&@{nyVw+h{#rlg?2^cIdA$ z%G$c_Li2xU-bRnq&KQxhKuMsox4$j?yB{#D8GuCX7Z>RLd-&u_K=P$l+Beg0iQ)>s zS-Ls}iRkbhh0=9@3sPPjwP%N*1!zrJv6)yv{}jL{)CBz!Sq zjvU*j7l>{82cjhgw-uIxk_@2ukg&%QyJd6gAOvu>qN^j9^lbfjRg5_Rt*rs^-)fo$ zGP7XhP$O}V=*e3u98|=>R=P69kSI@z_)%SL)|KmUV!HC{eT)yC0VYs82J)pxuiCo5Hr2x=_iVQI5 z9W;kZP_Fya?Niqh&S?)a$$Y6_(ZEKCEA(7^>z@4UxJd`bZSc5Qc6({nypX>JwKSsV zQU58b=f)x$&VB@~D0e;R&pv%*Npg~;aTfgT(EYurH=aOMV9M^b2s06@+u(=gPGB~t zFR1<(?KJsB!Xg;{E(TX%rYF9Jky{TsTR>Sxd4k*TPE*8!ET|_);O80a0Z0u8Xr_!I zm4-4p`l1$ZYzBiEwHA23#9m+nGA39x1C&a~+k$`nm9DKGk8=O2+V0Mb3apC&T=fjD z`01LLJz~a8O#PuR+oEjAqfL*AhjN1%>$t$;JX>HM@W+D}HBu;i(~N^;*^<=f`0l^C z<2VAzscc1NU(6PA9xCu`&Do}$qN%(|2XI8U`s@)wUjRHJ00r7-d@2u40b}RMpBo<@ zCW0oA4kx(2@2NX{5JiaXtF_YG`0U`uZPf#R{-ydnT{H#gF-J1Aq@xH11b8|s0KV>a zDO}rxwnXcmL(Uo)+nG`mf4h6Cp-c$*tIPr)CSS|hqcLR*F?S2Ve&@wEp`SMqF)({b z;^h>`N;UzT#(GI){d-&bU1||39cIFuwWS!p&9yt_nDlm(oj4nJh{UJrct(V2T3|#M z#kRZ)(EjMakXL;aoL^kKj$IWCWeyC%9&#B`@^CukJfdQd94Gj}BH~s=cM1nphaYz7w%(x78$zS7G5%tS+`o4} zK>}^=A=C7li+YQ<(Z^2a|3b*Rtc?epI#dy(MjfMO$y+}2tjA7xRwd>WKhuGLqjDP9 zcNY^+3eB2sHGW0fN^a2Bhcawkk}tRKD*dm)r8igT=l{OftOB>rmzA$L>z7_&c325J zKVxpZ=3D3tHl(QihWE|_3e_}m0L0W540FgJD~APSnysH&*qQ>c{9neEp7c#L*WC$% z$H+deF>M9CVdws!(rG9dz0Dtevjk4WF$)ZG^xV{7XP9mnj(?iFxQHClu*`OPHwRpA`v73u#_cT^TnAq`KOG|M*IxCM{ ze4Zu>q#*`|GPc(dkojqWSEvT3*@28X;K-9{H-`F9a!dM~KU?m&P^r@`;kv%lq5>*t zO%_I(h#4U@hb&0_Qi>Xg)2X-u#EN=p>>^K)4-!sLMzhNj|V1 zop!mkf-9RU&)^00yQlEG)4NHY?mwHYPf~ z5}lz+tkv7paX|=ZS@X43>q5&d%-pQ3E|X9{`5be6fMK(>a~K+YOr-vH!cVSpJ;Q-^(rgeCup!o(XmJrz4JsL4V{00FCXk<}j) zuF?iPuqQp&_J_oP^20bEb;wD%Aopu0?k|4FGq*(pTy_cnA6H+&76kxhJ2XRg*TB%- z-8pnfcQ?}Ajnpu7w;&(_iXbK3p)^W&cZWQ7_wDy~-=8@5o_kMN^o_bBzwcE8{ZgP22o;`-;`Pc5Kmf^b*V#oyT{3}0^GuCM(&yoZi7Gl`ZTOgdf=*)}Sq@oT)MSrK}Z zgCSPtpFfcADH1|l&6eLz)Wl7An?>((+m(-=7W|w3Nj3b&KD+=jVfS4}UX;u=S!`Go z^05#^iT}VlfA|nyp3U61rPG&8aQ|%Eszo~KI6;_htS_@X@@<^sT!NV4lbGWg z)E)?BuU|nq77F}Xj@E!=wMjF#td7(Nh{m@xfT4xPHb{39^=uJQ@AS}0?`*nZ(=c_{ zkHz|+*iu`HYer!P{4+YWM9cnys#kLj5l4CMSH3H~YaWUYef2P00ihnx)X9I{j6D%g zPaUQnC9AI92lkf_?_P`+kd`~_ygMZXTIEX5!E$MLc@Z3Brj(9GtHeo3m!4E9iJ7fhsQY<*c&lWySNV<$3P-P+;OK44?rrk$5ihZ+w&5EPA{k)ktTQ6*y+XerquwhO?s{;7p2is~83h^L#~%^x`Q}3M+?ip2AU?$x%;kqN zRF*%)dox(l4?_X_0knzJp6U=<5^(e=vorTwy*UzI;9A;jdrXCn&I);x&z}h0Il4Jh z{1w7=&`aUFyXVWAo)7t}%Y51^TP`;IpB5rM_q-hJy}OIbQoOZvr}VkB!rZmB1A)o55CKcSpi+nB}gm zvT;iM-@HJoM^bfC-RGaV4it(qeFjm2;XzmriouvHZx;yT<6@GTQy>HR{ zb0PGg{Cp!WGCIqa;{VP9_^IUc{*`vQI(pBQJtG}?WEHMAgXklE({tl;!+}`F(I@7Y z`jkG!A9TT|MHgKCYhK+O5e65CF(9fG6nYAs*LUFpyOIm9v_3zFuu9G#z3&Pb3Y?6r z9G`u&t(txiFxte)0?|DKx|!^&Xb1zF){3BXXe~OBne>5Z*{sdzl9gf-vyziqc4pH) zB~D+PyN?=hB?rvE-iy_pmMnHq5S-U-=z}C=nbvnmW5IY~f*5+lbsAOJnrX6#D94%_ zT+y7hNbXpJWe5+VU1gt-*c}O5F2*`xg);iSUxGZfPgTA?{iJk#86p-^wOF9I8K+5|q7Jp;Y(7^KBRy$DUj@Tl;Z50H?K?lo68n_AUVJ`{vLLf8 zvt2le#&e6pbNpqZ?a5aExH(jx&*M+px*tql$+2u%+7x=&e0$h#E3=h+7_V8;tx0|! z?*JbcJ4!epK0j&aI+-Llacg~yy6by_|G&NI4Vqm46Po_2+@W-N{3kT+zFj{n5Z7m< zNxK}G!~Zc4#M6!)~gKA&qj~5<49DBXN?}hIG-fP6OXL>E0_lBpDoa81g|C! zv-L%j2kTs;r#d8yj;OUAutLv5v1ESgW z1Rk!3O1nZw-<20|pwSEg(G6agj}&Q>_#^aiiu;&Wlkh>*Q{fWp0AHKv;LkdQ zQ^`HEmX;LD^Aj=@IKhF)V4Bjy7stdWitqFQ4Xc9~Em9f(;U3sQiTov?Wa8)*j`tyA z4;xV2(qz0pMnyB_h@{uZj>MJ2V8B3^`zeX-=ek)~TDs#dnxtwo;4Tdz*7IdsdD5J^ zj{?~Se%tG_IR4onI&flY1hH+eiGAZKqbA=aAxOq}?Lz@TEoH|HvaqxJU1bhm` zPkcA;sgOTI_WA{eEC}bot@ILZO!EBP-q>D}F~nqUV!pTQJZfH>lVSz$<%nE2L%J5v zG2L?699{U9;IvQ(r(gp|DyujfUu?L|cw=DiH90nVR}Rr&BoaW+_VF;{AolksvyTFG zxW`=7Cr(wkNgm;R>{z4O!(?9jGK-*@e`)?fg44CfT2JH2`hZ}yax8S$-NChhi#30Z z*H!AH<=O#hVfKr})80Uo0>=%R@EC3PD9pCnDB|krr&~9IKl4^5VTb)c;eB^IusM3;&^aUb`86 z89RI8-7E`@uZidVCl@v$`zopZzJIyKUnv5sn?MV4j7$anvUorK0PSzw&`SLh$3o=_ zka`a-`zc;C*32M8!Zf6qD*0k|`13(!drQ*rus%rxFYj}ju!rU+G3LfA+yHLsOko41 zE-eHFJ2i?~;iM^7l48-n_MQ+2Wpo9LxFTlIu*1?l%mH6_D4CPSMV4J9YZN3uPM0pf z8fW8kqH9DXxCua4)Al0|f6wJ_5o`vv3A!N)pl*7^Tl+Zf)3x>3HXZ&kaf-h2KJrco zNd>ck!6Gvn!5b;oxcy%IM!i0N7#B_=^)tD@8~nCJuR1n*RZ+LzDa?;KGp|(EwPW^0IYxLhCn^y?eE2VsSyJtn{+8RqXd;U$(2+Xb zt6C6nF*4UT?2_)-(S?y?3Qk|tGdk<8+ATAd63UMuE5?^v?4V9$N79FGxmyt*ZNVf8 z=sre^e8Iaf$VqD-(Io{~#dt-B^biCWSz(;lX}f)PyHQmZq!*4p3GptaA(F^enncmt zzTr|}wCI_vM*!xEA}E+Ji72NO3&wRUyjazUoQD^9PQ9UEJa8*Q58 zN6YDu8V06NqVS8W{33DIj6cIglsXYNs+`8O7{Rr{a>wuDMPxcs5xWN$ZkEp8uJw)& zmaXGy-hJm}^p7m!t3z}@0L4j2tTHAcblTU69jRF_tBKVLHeUp7Etvlc!NQ|u#uCkf zo6u8=DvtSm2E#nfC1gXDiE0v?!^Uifl6jTKvUCb}rN4|Z00h`BzC5n~(;j3WHr?)EhCB*s4P$)Um%t#dC^XIS>*$D4mU)GT_(71leJgn&XMpWRn|G%<){u@xwU;h(G!>%(IsK=YH72Po- zdZI9KWQ&uje@TYUMH2jh_1t~9wB>r%7_^~`Esn*WXz&Q^4bsWQ`Al}n`HZzzyh{7j z5p`1Dy|+hwc!Lsn6CBj5;d;NLaq~LTU9}P=$EAjL`EBue29~QjCc-meKjx7eBaasz zvI%eFz9p__#TyEbe?_pnr*@EKK>`lDu9Fu^idfvIDo9ReSinBlr1%E)_e14@qc~2+ zUr;ucsxe@$% zzA{}9U_bx}pG$V6ALl}z4**(|0Ex4oO9!P z)O`Qc<#ict5F?sGZ~tN2_6SiXqjQEkUB&5F;ao#_O{4LxD96n3kZ`twpvniY!95PC zp@UYOT-&o2);aC0$H7C>$_zgd@ z`ZG-!PJ)c=JhfzW;4kq84W(YrPcP0ZVl|@4L2=FSp+=C1?=(M~m#MXe6iP<3sL&{8 zErD=&M}q&x#Jwf%!O8TW$qZ|WzgJ-c-{bhcM-&d`gFnLGtxC0V#%9w>B|j%N>>&SM z{ckmy)8X~DP%P^elu9Uu`QE81ta5r4uIs~_fcp9RpDqhJd8)TlOX}UufFEvd7h&vY z+k3fctR}NVh`6QYDp@wX-A2v|$`K*36$R43iINVx;G*={Qrm~_pwi%3sI@d9AOvDJ z6hJ8eU3d1_!z-j?q*9#W(zdOY7i9$~6#7|zt;RvAEuz=9ih1t2TB`@%Bb^(jOetI2$;K{P%E3tx!$lR5S~TF3qZl;I;UBycR$|K9Ij^CQ%XSs+ zjDS1&kWN#C(=HexX|HTYG^1d91;VmBH$iN8r|`&*!dqoLOc4p6`t2ob(^@DYG8SYcW7VLdk;Pmzm06RO1LNq7kmtB z>?WwsQoi@oPl~Ed%*X0+FthF?X)sf*u*_-F8*k9FY0?6wgTFY()RPwemn#pw#2%Ul zy?^f5UdX)zR*dbQbKjp=QIkt(1=u;VsZIa8j7UxhR#2@ONUbn{R<cIuC}E8*zj%GT0FQ_=CK@M}SdY_2eb6 zLp=Pm9=P+V*T!j>BdK_ry?u0pE?+Bn6>vlaN7_~)?)RGzs|zvYUsQxf(uT!2!?{A# z?;Tnilyxsx%5!{8PZ44$4!IZS2JtEgrbrT1=?s9_gf)vKEafF(DlO0UTiSlW887EQ z_Bmg!gHxK{PH@6Nm?lAjAY*T-#P-89GaNA$40AzR9U5ic=4ArgUy}2d%}o&i0FFze zoh}6T9uOvaK6m$L1A)YEoUFfr;7<)!-v}~XP4S()!_C)wvP|f5#ha!^e}trUrx|w4 zoFf+`j(Sx*0nhNea`_SZ_gA*^^_a%!F)Thmb*Z$ajz9t#NvOW35QDE0>$C$ zSiw20`xv(_v%mO3S?dLh79&WO2$LI~tv^ujArbH62?2l5NL=CgngM`F#18{onLP+e zA0JVsEBr9lulks2mORicN}h<3C49_CQbSk1iFSs11O~hO>GTiXyaLAWU$JoYE36Xp zbzp|TRa8hz?cz*!}oh`WoIbVQWWWX17J z>_ZEmJA;AMU(ucRcaM@cl>d9XD?c!w2HL|jVgH_e=-SO z?Cv|uZ$#g>V=vm`Oak`EZW$CJ^Z-s5@+Ng3pNHmg(O=+(n7EUvBnpIJ#gh1`ek z+l9iQDkNb8JP`(^Vs%zBzj}pF7phg^*>*npLmOY+6BM@z!w)h|3JGmcGO@_5|WLyH$@j)vShIv7{7`!5)sJbGzMe_*L3x$h+4$T zBdQD}=XhvI=&za4g>rWOL6rU|#$&tOf(58ju)3ggjhuBLA4yj)p~V>BN*v~t;2XGN zubJ9zg(BP+yge1YkZRL(bIvp<415gfxRxF8RDA>GJe0gVXpyjL(NZ~>#edkY4>%L~ zx&p`JM){-l2dF9_&y*=5I7>(KV=x z#Tj%L~2msy(wG2v=Zf%s=)Da5KW$~eeT6gX@+R1I!;p@C{dcwW#n1i_qp?RlQ zjC(eO;2NKZ{N9$Fs1AbT`R(Z%8&5!&G%E{oqN>)(B|l)da--eki(mz6P@9}?o3BSz zQ_@(TXGech!U7js|><_XZP{COVRQee+kEWtD6Qbx!vGIoo9@>h)#k3ithsf1h@4W-B@!NSX>AEla zS+&ZB^7e2S?bEson<;{f0Wz1C@tvnWi%PRQoo@It@|O$1RqYP=9wnTH5M7z5Mh^?f zZP32RUO}xBNd3kZ#Q^smzARh#pG2kckV;g4gQ{1t-ZETn8)`-vN5t$14N=ULtTCeZovQ(#NyO%fRRoS_jc4etg}o1XE= zT{pYpnsiBJFMl-I+m&6YPWd>I?(eE&_eGMyl8pjyq`IFONssIW3l_ zgcGWY@QYFp^_jNx3GjW1OHB6hzC)<|iUMq7W6MCBfA3wdk-mtPvmb8jf^h7x5oOp` z2RSHX;aA&E(BS#0{vkQT2QzOPkiGpnwi+NX=waggs5%*d+)aa!ghxMiyZZ84e79;k z29t+i410J^Iu`4QBfwXH^9c~7^!_Sh!tGfk|}dtI!N?a%es13d<#&~n>NHPs~f3Ro15Gmxj?J%>tY z+(q66JC|jL1r@qXPPfRT(L9ZUfuq0ln_45B znPLtvO5dg2Rt9>q4L}gTgjZ+u$obH!On+a#{9u!fYKU{8#Bhw?s)3L1TeiiObys*8 zBgU9>hF%B4Mq_8brJeTt0$J!x*%68hO%>*{s`n3{vq$6M7P2d{oFtKY%$x$X=d#u` z3qaPa?7H@mv(#KR-i)wU^6#1`%n(Dq);^R)addh5a7qQI(1@>~9_1t}!e|-19{&l# z_N3mU9Q_R<3ykddPWROvbj8B{%kS6{q&?WyteP`vr18XR24uOOBiZ&D7v(AQcwhVU zn(^mfs>QX8I)|`Bt-4Jr4jxk1>!A@vmyk^$pL{P*oyOH2|DYhZGCs(?ICop03Xl9& zc?wrXANqWH4r`vVrJnYWZ~sXzVQ%RZ)`V(+$OcaYml!VJO?z&M+t9xkH*kk9Y08c_ z%Y$~r$8*X6S%eK+GWUGsLnPl>WbIxG2lP=KM1`SOl z@t(_~$Zj)u^d31x_YiPPbY2F{yjMK5S>{L&Fs>Z>cy;(#kf0Go3-=H45`U!eV(Fj4 z*IBnQ5gS#GkGVOfFiad23>iB_OCg0vD32?uMrM{<_Pn>L7sd@qm609J&?N{eu1&_RPx( z#8L5>$`Oh%Wt*&-<{+%pjUA_Vl*yUP;2P-QS zwNsBI>;V-Da0Xk4LR;X{z75(@+?f*!ZmXALH6kzvAp>=t)Is9B;EiEr92mcP*u=h_ z4zLdOg^ad$4;fuxrO-;tsTF%c>G|D{XbejPGlzGJs4W$%bXz<`mY;F!zZ=Ybh=U?2 z@g)SEZ}A5_FZj?256|YHYhwr=(v(p(khSadA>S%XT5>K8&*TLz!Xs_lAR$0o@D^vFr3b@g0~W+%pT)h9~s+XW8BcE%U2FmiUo*x^x8iq z3M#|1J&tSVNqp$<12@n@qwW9TW5)?4^TPaaZ%aMzGjP!9o0?K_vnlvHcv7I@jOwau zi^z>kzmrX2Nh^%%>sS}QD|87Z)68vTUmj{;Eeoay* z)kM>(bGmzVvdVi!<>8#xz6iehvD|`a)Dax?jrycDSNK=swYn%XsbIXyQU_kGdkcf*lcs<~&NG8C9z$OQ|9I-<;}Z4J4}6bnh2d=BK)C^>S@$r|_Rn`nBG@ zW?ozuxk!Hy9}aAky3s~Kor<}X*4|73B&d+JaLZkw%OE~CFmgg?(qZoc?4tJg;}?Ya z`%7*~>sDrj4Z|8Zj8hkE@rjSPPKQgmv&z49=5qmsIMxHtpv4T*ByOk5!H#NBZ(@55v3iiIip%jNIe58!M~0&2kbNYJ!?}v z;9kklH@-9|@4j{G-JRJgDN%dL9>Ko{RLIkQXW6zUG|A(baMH*MjPG`dHOOUVD|2i| zEv4(lX*a`~{a6T}H7H2_-?*ygp$SV@Of7t{%5H5kjDIhF zo>+=MU74KA`bR|no5-|6oai_qCEH0~Yr-B3dX=;RnUmi|$Bwgc+g9830vrg-zNxfY zt;o%Bv!A&eMHW;GR-aUGe@uxCMa9KrBZvrkb-Q!nwXuOvl&%;2aOb0ZRV`K~;Tnpg zusB0zc^$_p;^cQ%HHLMVsi=knKFPNtTFJC5YtvJ7*Qz9F#9hzc=wfuP_KO5;9Wge2 zPaML445KC{hf~)7D;z6!UkYQXyt2lM&%3;?h=bB#W%~sg{y#vqz->Scra)wSQ&zhF zldEJpNT4U*94ur1St!*o9AWZ7Hqy~dmylY|T*oA?%2HwonQ8K)8!5aa5ONP3o{Dvh z#RxKBmI1#rLa1YOr;F*Z9vb4nZBZHSP!VX1BL>6MJo$e0VrQ7QR|`Ijb+C*e$|O1U z`tf883o0R99C0g1fociTQt`UmeU`)sG;05ZEd^g*ejH*_3#Uql4+02^J%dH`4zo!vUN?b{TktVzzq9X80bfF~tRy1lIF|(rdX*yE^3U-KuR350MeM`s*$tRP9n|(BX>|dtvcYnEy8IGrjw$O!ThQ=bLn#ZW^=J>x)cCJ62 zLe>h9_>=MS8_Q-lR&3dj;pPb7Vl>#u+1y5!*X(EUhpWDVOnHnm4>*XWoNDK>*bT(IhOsG zdXSys&V94G=5LVLU@7qQ6cSIp)=E?KFV`RCnocR%Gb&oELA5d#22Xui2Es^?cag>- z!^@>#dJy6KYrbRaijG$xK~;bO<{LoM@WGfaUt?6L{f_jaeN^UQ+1GkC)Zmb4eL)zW zQH+&mprO}TRwr#jxxv!OnLUJ}&7@l?cbU!eQjvtE4!hs4z7VmnCfXJ@8k9os1$FqvHHuH zMQ|C2v>Cd*)MR6*r3YGos*N z?`>Kq-C@Sh0o9{crD_UuXS*waX#~F-P?e;%>a^=6aReUs7cdSw%){?yx$S?|s~}|W zA?uyZB}Nipp*}@qfZ}V#@TumY8P;__WSzl9Mz68a!lG{_IJ6l6D%4u=p9m5!+2FQ} z=ZZpZSz9+2s~=|2trwk#nEPtUw;(n~dxJS^&y}5w$c5;nA(Pqh81U678$N@L?DwAx z`9gRx9Z5U_ilm&u{Z9qwE?cMd7!n3NOuQTxoi)}f&_EgR(?yQ^LFrnx>+XrAo!jS+$tSIfj{hvGi%z4_O#S~_4PP+kM1yKxg z{)*8Y)4p3lCRaSUn^x!Jze>NRp4XSvjGgjhs|6SLBl5^eOQ|z?=G&5r{O5AwvT*Vf_*GgttI-oi#NJ$*x?;$udRN2gbQua){A8@>ow*;;IGMZ?w7u>_6 zSa)T_nRE`8-F@^Er}X=)Sk)IJ#))cLnMrq7CYqC6rsT0{Xel`MS7L0rRuUV-Pi2e| zWvGDKoRX`A9H`SCRgtUuIru7`f!;LsiqTe@o?WYHXsW@4tiJ^|@XSoJ?t^1uUPfsD z1M2?oD%rgv)s0u|S6;Y!!9GXny%ayxuF6x8bSyDqH4$q}ej26u4%L`p^W((Zub*!O z_YT0(kvAdymK-T7Unc^>U9?eO8pn<`*u~+}u;sJZTIIChpj2>@PmY+hULit8WvB>d-UD~!=cLuZ>m?IX^IEDj&1C8E&x;nOn z{&^}ceCaqzWa&Kc5jD9EYMXpkmtTT+sZ9@1|Cf4!1w0?Qkj+F2)vZeZ0%tAI;1J$-m6dL?Pd$gv4#+sTb7 z+3KDR>?|RwE}Z~9S&+$vEF4~}H>Vp;F=w7cz$&fEdr@p*IT^6PL_4;i?Gsfa4v3TY zU*ib!*_@4>!O-o6EM>RVkH&6q9mO^d+XosJrTTsbGxl#>0y8)THgs{C3`Kj@Rr6t# zTOSQYUlI(WBxT(SM@hG*fyhmYP#<+y>JwrMDpy# zv|KOxhJ&Xj&BfCAYoJMOr_Zd$+382NY*vGny~z_1?DQ``o|E*Wl#RkZIZNI*JG3V; z=_JCMgt_XQ3(K7p4y%Z|`C;BV*{5W9?xd+gcPgl-V*i&nUukk5_D76kH zBgoleSqmErdEPHRznxw9+9s3R2UGA&EbBM-payX(2}l*bQJxD=mfK^l$TCL2db~jT zTMg36Lr-o2Wl?Txw(|E3NVqlwvTV@kp76dqQiBUaMc*L<(1*%pOkC+ftQ4%>pQSoO zow=UN9gjhh9+xy_WIl(MnadJC=zcrh@`6*U86gduM1+XU?$E4@To^J48&W3DGXz#& zV4tlRr#;>E5GS<7;@HzIw<*RdMXKy}$9hrJ?AnccMqf6h6!@1Z5R{_=oq8pD<<2{E zjTCN&$0zS+dhwnvhqC+ER-&H8=>A#hqa|r0SyB4YkzVXlZPZI@@O@9qdauNDOWAcQ zY}F_uEHlXc(Xvwb$SNUTcO|;D1_fpBa*H#C!V9oqU!bWr=aHb5_FAJWq4x=AUesXc zEVNyo;lj1*isT$6PfK(z9qV0V}IhrMc+pYnEn!X6(+v?8xR?`U$n?O-NPG*u%n z=j68ctLEfg>NY&3JNgeokNKO97U%WGjABOzVM>4LsA~vF9^e$`Uj5 zPKMLJ`zqx|8Bt$r#Kh~yW)PP9QTzp6aN(_ z>lEHO5EgQ(ZZg3T+l2Y9xK{lgu_a#yHG-aTFNEo{J~L))0g~=3pi8xXYn#5SG38KV zfoKGIX9fGj2rD{sikN0_rwZQ9c?9?O$Y!@zuApT#*y7!8^oIGz%CmY|ea4io4b;d< z!Uf@*nTqT=$9Uh^uuGV4Sp9GQ>>l(V{Q1N+|L)wb^=y5o@LVwX+7 zn!>UVe?E{PkGNcBfa&JJN5O)+$9uT*R$6o|7SxTE(-9BSr*X8EA@-k9P&9a~tYgHjlYv<&MEH=*+` zwrX&H+;Vc78tH|x8;@iAC0UEpmhQZ<;m;8Zj6FYyo-xlCVb-bG^R6MY2APhRsJ#W{FbGA^{q^~goZepp$5p}+6vAtPkn3tjYYPb*-KXG(ib7o1I7{r26 z&#eF1Uj%%xPn*{d{-EoWaaJ8u+qN*}Zmvtp$C>a~X=$k6U9^uS>7Ng3J&Mm;Y|C{o zr~wj+tI`+{w@*L0ez^4$ddy1i%#9t%PmNel%qoz>TEUvxN$e?2bg(&7yiJifg&%J% zM%p;p`P5%^Fq6m~(f&aFG7ptf#+FuKjrj5|t&N)8s^{9aX6@P5BQUBma=OhDxWx>KR`eimS4%4ymEPHNEfEpHVPjD zM#=F+Y<|(IB4&9pqJTq$!vW%-tsqNr=U`q@b5aKePr*2S4wyy^f6`!Vo#b+6Gs>M! zj707Lj6=~}14lUu6k{*SDH3CnXJIG-O1#d=rcMiYMZFNdR8W2Xi*vyx+dpuu%XGJg zARHMi7O1?e*em`HBA|08^KG}mu2Z997$y2t>RS>)xRc2kV3CEnI)|h%T1fBG9~oCT zTJC7F%m7!x_87~{Um%?cFSg{yQd!H7Rt#}GljHb9DU4Ey{;1Cy zzvJVnzYJ`k|Gjui1e~X;*m;i~KUR}<6|By!OMC?^U)OR5&^!ryd>(@JePlh`uYOvR%Yc+&-Vf2c+x)xcSO!v6I01LZ~IFZt3ez zC|W9}oURHb@Lp`YJk~rmLXU!U5wl5M!Z(^!{Z;1dWuP&HRc_#5Xx5+AS0(GSprNuZ zv5ahf&@F?9*@69LN(rprRn3mRL7oHmYaQJc9I)nT($AW+4iz`qH|KLZJZ6M84iSxkL z%7SIe**qpo-qOrOayJvFDvX5a6?_s6hC}c^;%?2fwXI3b{p$xe7?0=ej;`<12S!5YZ`RPZYEm2jNu-zS$}>Y` zAeu zC%8&5hq>mF23z1e1*ec$ACs`{9+7kr)>6O&X|x#V2`|eoh~X{QLkgBo)Tm-C9gkht zAAgrMSSVQD)W!zc9IwRbL{S4J3eHTh`uv_?&0ho&38TY&RDJikcA_HDZ7q`i$ zfla*zcLZiQcj^AYJ1WafPZ=jN%d^JpAIIknf}?|!>cqQsuYvRR;54I=U!ibIpxba9(uyYOBRq{-4RE3=Z>E%WAep8^m)8fS>6dB zFlD;`fv$-D^a%7=@j+w6sI36^b0!Z-EOHc0f19faqa?qC#}}8$GX474tgQF)=pu<=J0y^qds9g2{$Jr*srOWE^U z`g{~Cl|F|g6tLPMozo3!BVVtw&o)p>471dkuaWdjif!Y~Pt38`dgJL(2^=*#ZT%+B zjh9hrBwQnJ9S6|8*Mh5ac6 zxP#W7l9s~gr8TnMXQqE}9Ta7`j#_1{T0yQt-7oEWMJ;r|w;*DxQ5p6-NHrha9Ze&6 z%V2A0gn%FtFsrBgXqMO(yM31VDEia<+Ez&={#q;UU$l3wOr8X697*2>$AmtGT`oBG zfpM&q#HIvq&<<<5S8d35_;LhiGyNYAL&b1TF;a_{wLq%#PX@^u{>ZMcMh~33&z4mj z+48&3I=jKUFDxJ>ct@C18U;rl?QdNchJf8fv8%Vn8le*bk)-^gAwG;&Eyp%1S#PQYiDE~3G_4ibcd{6>mU9Cfh>p}=i=XbN6mkQ(#t$l>yN!R z|Ks!_wygI`m}$(NDa~|9#ZuTFOT7AfLkDi^;O}~BI>za`%t=e=3TYh(Be$lx)BwqU&n|whYNY9%u;?@gdnMI5JY7LmTe3a>@Uh1O@qq4?ZGq z!u$2YB~&1PMgvyH>!Kvg(`K3FgB4}t#e>apC-{A=$ z21E%1O3AW$1-j)`#nw*N3*n-5s`%w56E1S&d5JbDB_aI5{_v5f29+ zTm-~d(e7h+Fzr=tSOp}RetqPqiD9#gbr;Rpu?Z@&vaRfI!oHxHT%dqizzg&Cv-`;X zco8`Le*Gn@t^Bqp^GuWvajkr^r;?H_TdAP&)?i-G%QK9h7900+52v_sGWNS_(il&p zR_gZB3_?J}&N6d?;~`+NY*lU8fPK#Fj2zCDk#d0|$R|v5h!>Aw!-{I4j2$qtQT03X zdiJ~i2bqMa+A$2KZ%IZqHNR5%qKBY}(&b%)TVSBdd(;hKfr>qgisJUi+hpqHR%np$ zzUD<5sxm}h4NS08e^zQUCWFyuU9^=UNNP2ag7F9z-dZn>@pk`AS$(Sc;X`bayMluf zoyl>OROq3=OG*O4mFz)&!0%nL<8;z9-!*ipUeU-T~FPHIFo9Oj|)+RumQeFcz&k#Kvp6;_I#kBk%4K?F@Ui zJ8zdB`ka>amlt;0- z?Hk@*_wzTh)Uh(u|5yIfu+GurVv0tZXNL7)N#w!sPu0(1xb2Iti4o5qO!#~-+E(3_ zp~B$YLBtg4H;jfVyX!aUd|u$T<3HJ%J!<(kcJ+;yh8I&KcB`A#kM(ZNEDZ0W zaP;+#R!znq{QVu2P_eI~v!luZS0T|&ndhqA!OqmWA>Q6FM%+9(RJF1)NTVmYO*$io zg9!27x*L4Ys=aG>PhXE0^UJjc^J_(^9LIJwf0pdI_;KyPo)uF0h5m(wB^AbDM^8bu zjs0S(`yDTwqmK^-*PMVC3fadvobi~x`a|&x|DZa{wtlF>-QTz4>86Q?k;x;0ysTDf zDlf7;41((OSj*7W+neD;?vZZch#@TzOS51wtX5=xriz0FcDtS$_uNyEMNM1qGg8R13ZV@^R_+W?v>M_4p1iKa%4b)Rm$|N3}Vw{!rPfbTWm+ZtR|x z7fHySPU_B?RI;oF8DR@)8AGYoq>kTaP{9q5+3Oh@y^<#LZC{?XGKG^&Bhwm zvbgp^_Um)^mD#ptD0%i*%e@VkKOp^~ww?iH{9bJ!JmV@;P|tw#zTb`Mr#{;G^6c+d zxgu#Ky+_n3o}I}LSI;PE5tpL@U6 zswg?Xhq)vDcrs|0fX>|%h^>AYTR{C`Mq@H+b0n_lp1drX9`@qNAPHhL2)d!cjQy5I zb|qA~>VWu^Dw!Pq9hPFkB9g*g_9=^aBYv*N{X&I z2b@EppW(l)FDFiX-x=yBjrv1%@V%nqyM<&VK0m<_{ltaPqmIGaRW56nciDb3)XaWK zT}heeawApBH5cNF$bLmCu?HK1sTdYmLN z(JD(kkk`(x=}P#HxPJ- zW^Z8vU~+^gva8B%p~|{1IAP7weVOI13bi(R6Tq<(%(1S7YUR zkzb5J-qr$7$^Mrj<9~9CckkqUy5THtKjVI9#eTPT!mGhzg;1>J_(FIE!N`lB!yw3f zmX#rHXz6q%zj*Cz!5Ws;W?(y&5WtXXm$^GY)Cl46%pI(_AH_Y9OOQ6FVZK|ayI&0- zl?|p=j?Zmau0yVO*No4*{Uxa=N3Cp3)s;_er+0IT>I*=&Xmq$+)u~VxP0qM)W zpM^K{pcca@h-{I@!<(JSmgfi)lhr>ih#Brv6AZ3mA1fnP;SO)|G$$ch^(Fs&M=Fc% zei7Y6r8NljIb6QrX!E&HV=;KOG8WBirWkSvsxca>7GEuEo2_>ll7`vhunz}T4fo_= z=RFS%&d+Zi;F$N%>O#j`2GeDo&Hxk`X$J}0}k^1tZ$&t$Dsmh-<-~M zOELVuX;^E5ec2~Bnmn#(kE@(x)U?ZdqZRk1C2{n zX2pl4%8Uv}*E`XGin&~@Tt=&}{@yNA1pLWNs-JmXfwzV303nL^qOF(MqEyA3d=3(K zNa0V?qa%qf{Qp@CARf7nNOh2FqUO*09D}|OnM}7ck>cj+SVVfihA7yeE#$Kgce`=I zA(|A_(x0Xq6#5&0AmuXL7-|bqpVkwQhDv?AoX^f0PR*H7APfd-lA{_N*AJSV6@!Hc z2!l+;WVntG`C5O$Y~>)KEieMwO)>9)^aSSahO!ZA;J8PzGenr@{i{G z!c&T_IoTyL(6a(IC9HhCrIr>%+AroO3`1Tpg#g_4dkTs0wuehi{nu7H?@@-jflAG} z`EozKr1CC7hg+im_BR>OL;3f!DABsc3gurrWaygLqkSM-jbh7JBa`I1$_$qsYL)F? z84x;YA=g13l|(dBz!eivHl`ikLW!W7`ck*~RfEV;t0>{eU_?>6G=-%xcj65-Vebo; z<4xtXfG5LTkbz^ZFD)Gnmg8KglM;bk2AERzD?M|8F>6$Bm>5hB$IiYk9twdmF}SVr zHN#JuZuaT44R`*#mxX8{$(HcrREJWqM?4#r=upI5q`1j5zbKPxm0%GY6HLTPI`z6m zB+u|W%cu@-^I|>Tw4fjk&)ZAg+%BFZLKMrdZNRkqb}=ScA-%bjvzfs_qoXko(d^k- zC$(ugj5brkZxu=*KEPr9M6!*`=}d;x{m}qZk039Z#~XG>WBx(8T#7*!TCVG`rp|?1 zwP{ssDNLkPxFT>Q6^ij$=+9?0r*FrRdaQ4*%Nq(SNd1n9fP~-lxup|*beQZ|G<6Ah z(=!TOF2El0u&1)i>oV+zhrV6EuG}2YC+0pTYI;w?n!3#gE7){mQx}bzPfI4UjRI)|gQ%S&#L0C;N88J)ud*$(MLVL)ujG)4u)dNvYm3Dc}ds#?c7E+4#^_2yvQkMqR>HV)c{wLe}BP0QhpuI7u+&V+IMDLoUaJAq7 zFMcvpswBbKmT{ZN^W=n?E|UV+I*f)a297BQI?@<2I9AV$OGBjqwk=>txru&gMit98 zdvy1rJb6W|939?KMv9}&U$b=6Zrgt26;;a6=tj!MunI0|JS$OmDS399k!6x?Oc>qV zF(pm9xA$2#> zTAgNyOk*r+EqTNz#DwvVB_Xi9)2Wup?hjuej^{l+*N{i%*!qY)Ej%PxWc#$0m3UpV zk!*LG=y)qXTllS7z#w%XBafb5@8=b^ErI^v%e5(ydn zY%`60ycJt3$CE^7sVqsFC~=Qj9~@Ff_RZtdrBi&0d$js|{FBn7ZM{?)Yq5G)ymmQ# zh>AMPdrm6m`can%x{8172qEVguNHV=bl&OHN1X8?REBs?ViaNRo7C zspO>5P=U2FPU1{T6PW<3vEpx1Rw}j0yO+;c4AOOKNlrzdQ|5&$Mjc3XG+qDB9Bmfn ziZ@def6ppb&`10fmzaAMEEEMicv-8F_<&+9Ea{Y#k)2;d%g^gSJ*Y?qXiLJ%%ghjr z+M!O>PF$n=T>mr?X>(#u6QbiE{9cj(IH$Q_={*!Ie=zu_rmLpA4G0FoVXHIoWvMfH zvX;0w((Xo~w*6?{5v@L=5G3lFQb*gBIK3&q{V(Fte~a>&|KAjzW7PY%K*az6RCx36 zJfSijTQaB8Ut1`StqC5?IVk-5IGuU-!b~$FKwO^|lHu8ka^)pSGSK;_D|BUVF2yVG zMpPJ5ck0*8H~{1sQ}HKDrj85ZL??sQSx=xT1sfUwJuLbh9%b|PQPH{S>862W9IXle z;69cSTbd|GgqKWgD0=^%(ZpiXyVUtEa}h7*s%lg-Uf%N23rh(WIR(q4_U<_JTf>La zgY#30LEL0oHqD1A30x|g=jhGtr{QnM)unq;{1EOppN-wl3jx=~Z(Gh|(%Iq;cW?Ey zcRe~Or%${xCS629*J@iFf3=4!nf0Cey%m3I*pB^BPf+fNE}`2#CP?ip!!vd4iI?qQ zZR%4ylKvK9TfIkgZsZRY+5V?e;-Bc&5cA&T->i+?FZO6d`9&TIZ0yi}XlvagTFK9Q zUBzDmKH{X+iSblhG_hrgxi&R)RLGFrXbQoRftp5ZnpJDo=-JUsUCTIW)2}J-Gt~a* zLV3eaHZo?4+tir9WkTH!uE48ew3;)5C7)L6thiL_9+}m7dI~XeL8@^3 zJ~-=)(EO0=;Kf|Vwl(|lpL$f;(h7V{6*y6!7<5xh9F$XOzsvGR*P*pgSJm4-mt}zg z|HXLq;zdFsqUU#L%Um<9?;;+} zu_)%+HaHhVu9a@=zaIQTuI#0N*CKkU)S_Yy0DT=X(=~h>J*@4Bz&LmVyp~w<(S%sJO0=$Z?Qd=PZh+Xb(m#q$whpKDI|^p z#F`^o@RhUc(L-GV>iky?AP=(U*32#ZKWjZM+`F5QktQ%;s_kZNZj=}s{7lUz+S$(W zO-QzhXr@Ek?`Tg~kF8E13ys21QZ_U;VpoRN?0MN%9>Q9*vZ&qd55G!^wF)mv-G<`) zF&q+vUSUQaVYli`qBe4qft6)N@Izy9)Z%L@g-moP{RY1Cafo*Z&EUE}(aRGZV^+CF z-qGdRr?@nxGm2oQ)7SikK}&TjwXpdG51^m-grLF4OnIW2NxtsHd`q4kz~EMzBkuYP z3Qhmo|IUluMWDcQ9+>$O3th$z+?ZeL_FU**8TUw=b8Fr9LZ3VJIcY>wP}Qx>y0sSa zvJDL0@?Vzzt@k)g;kP|U))B zfR_OM{j1D&w|`?)z+Y7?^9l>6*^RA#Lr-aMZ{rmQmqDaL)q6Hp!%RyxrmD(i{g%i7 zD`WUCd+&u9#Xjtj0^Bx($_C?R~x z)xx`?IXhYoX-EwkIUwb^ad2NM=+zCUco5o^f(^}dtD)_9};|1c}P`+)%zfD3kw zEmdd>2A}uEWmtK7&|6_4IkG)6ih#6;jQh?1AFhXPbXyFl7Gx#gOz27fpDM&VWCj5K zKVLxz^QGhx*Z}k z8jqPA0HHFf)#+b-VbL>)6Q_mCPzYtmk1dJ1e#9oQB=Q5_HJbg0wvqeK&$s(@s8PQ% zDYPB`M}xZQ;a!yXF8aOdBDT*!BDliOU-IpmK@BKT?6~GLW168%gOb}Bm^5N1t}Pf! zPUA$eGW1k$#p;ojVRLZOxvljo^P zC=R-Q-vmN$QT}7u{^7=S5c7Im9Q6#^+9JJmCbWf5j4ru!nH}-UAMqvc+C__WbkRfi+G(N%z45qDDy3EPGCP5C$qWSTD>_%8qGOK{i0POJdmgg2`% z89w*y4_EVY1&S38e%y_xsX9$HeR5n*2xE|;kG+Ocyvy;&uT0mCrhS} zq_MXuA`ow8nIQVO-&;h5pQ5#ioHZxb@CHq&f)R{(=mGOK`qIE!>%hBVwNOpMk9@ua zE>G)@ZmJ@l$>_3xFIZ6jJL$GfUI)*~ zLpcerOu;iYnb70UqU-I5O*xU7*47mw@huq{HWUH<Sbl&NYA#SSV{ZLa9Wu2x;*wYg zr!p;nH%9MNy6JyG%6uOlOqrh2SS)W&B-cuu<0s|#gboElDqF)gHf_Vb?~U#H+a=cq zFmv)~vG+=Wt7kpxo%4N2hlg<`N^imNi^5?Pf&=Re=$)G0x6AA{oMwx5vi@Ii)HX&9 z`3wbAv()Y3#U)tc^%IcdIjW2NT&5f;e7 z&X~P3o92l(K$pFI8LvoaP+1_v_j#UgXS`6@!d~M*(je3j6@$9~Vq1x}p}d5>YQ35J zb$Z2So?0Ql|3Fr(I~3f}!1WCGWDNlP`K@KPcwQGfTNIU*X6@{J6;qPlEzColszh%p z9+6) z*CEoiN(jkH$gs8}I3Q7Pb2YU1{7s?$<10&B&PaXE^{8MtL_y4_J9%>VErE0pbXW*; zxRh2lVzSgQSY~Pdk*&REk5&{>JP@!)o0gH++1jF*kybhot5wCt4_D5?U2b91v*Ko1 zwV;I6(ov!w!CWmPKuWjTC%*Ms4i6F}Xpx<+%$DnMqL|S5#(2N4LD91!#*KQnqCxZo z!2KQ<5rCCPB)OXVLCnkVswZu?vZr=bHr98)mBMHg2<%|;mi6&_(?!o?yjc1&2uiX2 zNdpivhZbBlD=(pQq})#&rKLfLNF4)F=OW9Fa;N_ErWb)IY44j^=B5_AYA2gFP%+Q! zn@?+K4C|BOeOFq=$SY8w0pyLU06uRB`@G@}?z>r#uiw^16Jg}J(g30^U!o$Pwl1sL=H+2R-z#nY3dx_T9#@>>A@SfJ`2U!_4EQMTNt-m z5ij%^lE=8^xl-I|knvr+SH{!*53+0@Axuqic9MGUtb+x%8znsBzKeg7 z>7mxM9YH|i&ekuY_XaV0dQK%4jW^f5`iv)@j0my)3#>YS_%p)JUf*c8SLR`^#eR{` zgjs2w!F#l~s+Mr2G7bhxA%rXG4%x5lh72kQdzjZfPS^=1%X1A7^C!4CP$p>UNY0s9 z8VDraKIA4>+VWasUIuv7&gBxhyLWhY+n!o(?3?@t>xpP)w*C&|xX2spwlzLX-O|lu zeRnxpVVXvXbrnZ_bSgK?5q}hAQvT{hKL;97DaYW)3M@f8)MAqdZ+YInD*t0GQr{nZ z@h%ZB4nHOl<6LYq1i9;e#ZB?Rk}=HxbSszc(P4x>5VRxxw-6ND!Z@ExjhbWhlcp%O zF;jt;nKC4l*Z^x~4(i!-ECJ&7p=R~UV&ttDmj#O{xWPJc`B74ZA4uKd2W(aWR3&!S z>6`bpj-zRO)hx7z7H(po-e3yZ z?a&o=s-ZpOJ_>ZE&-qpU+-i^lJ%_ryQWmRV&dS|94S2oIYaZOsvko$}(o$LB|H1l-qtxP>b5XJZU`v1CclnuQyy&AD!(l_j^w1YCz`O-BrFf z?pprO+*(=_<0>AM;{_3erLF|HehI^yccVQ!*%FWXu7RyP^`{i;Wl${kL0zO<)q~PR zPxqBGmL6WC2S0i;UIQyf#gWh5ghqwxt{ zn)=VsV^RRlQC|gxr;P3{p_+`o`YuMHO+UeNwAuO0%hq|=K?82yc>%)tUn=O0g%O=I z8|Xzqr0w+B2lM4No+_Da1>9?T&4X}i(svo}l|)Y}WZju@;EnJt)7>TfZgt`0R)0@-c_ot3R?Z~kp!A!T z<;=F@dGHQw2S|>gd!xw$zP41=)QWh;nju_r04m4+T$az?y-5*13H>^h(9>6=4IlL! zhx(z_%%@L2HvcGk9cIfe11xpH*FjLIw>B4<-B?i1>92vFvtN(qDv5(p@?IT@*RB+T z{;5%KS89=7bozH}bqGLm*JI*mm+zvjYH&Z%9HfVlw_Dj(uEG;?&$NcJ<&3@MJb;zwUt_Xp?{D;Pno+I) z>Zm~dto6iDkB)_Z{?n$b9N>lTf`Iy?Xt;yn=1Ds>vTI{@#JCk7WhFtsd^{SLun(|j z_(5DCJ~#JajWgh$IC)5-JNS5TzlR)9){~aJqzxTT|8Imo^sDla?}18HI(pDX3qS9l zE1~)mpZYqedq`)2rn7_hJ>YX4wLZIoBv+o{#l!uttt*ACD-TkaE@NiExdr6Q_GjE1 z1}q{o1&zCrR!e@ZHCv}5|BW97E~HVAbMR_o=dPyDx+OFTIXBBd%*QWiXn)yW$A{}9 zR42YayoLR3EL8{5#G3Q^+&k)Ja6hUSIywr$THfN0WS+hAMrNiS9e|_$!9tbP@U-pc zy}Z#yMr-~3vjofQ6CK0rN8G$Lnvty}FU=?j@OS#TxX-+_-E5uKnvjSybEHqsJsRi| zOQ(zOV(uwXUq8i%k@9#)bWuDIBgbx3c4ECIVs0(;ntBYAHV=AQcgYzAl=ESUZ@%OH zXD^B3S04;o$5^Ve99kB}%fr+9MCHj1Kfa-ZG-y0ON!g}}49P_U^RTy_d0OtPPURjh z<8;?|NXOqef#&mB?x{a08?sy>?D}`WvS+dkizSobjmg#U z?uM-&u2=X|?UvQ{fE7>wYNogX(kn{b^L&p^#<_=xj}u^|huC>n`8I8U>s;+I_D#*$ z58Oi6Ym0~Q^_%tw(!FobQjYGqLRky8^&ZWyI&Yo#^;@McVo#A-x7&5A@&;@`4zjOg zTyLEf;l^Qq^B&}QZ0qhjuH1ag*Gn6gG-!jSq6xUoxnkUCgTwAt%yTy}|xdImd z23FZ6YvQ_xVSW4gm*JW)sjc%DeGPZ+rOv57>vn0jgFsbpW=XkpOC)YHzVm#cEPp=M z&F2NE9AaPGTykHH`v=4eiE)6#^tRq%JhXZ|y(GJd4QvEPqX}-&FuJ zyJaY1(I+>ufJ`hotSdN46PF#`d-+8oK?e6tY+i|WL6Nni{O>u-wmOmvry87id zBlx9qLlvxj~w^5j!sR z+Dtw@*s5$l#KQ${WoUEHe1o1+y8KeT&%Ue?T0exD9JlV7-RE~d&8s*MFNZd_IjuC$ z!IM46~h)m)b?M z)jSBib+~2?d6?&dYE<;~NndPbNE4=B`V5@vvt2Q7x46t*zljq&a_v>E;ecjBmx24X zqk2L&S$L-J9u+fqt|V34aj9i5GuKlajBs@}nX8mgZhjfjB!??(yV^_Ud)eYr+ z;ZJ&DPWwRAshcJaGRrm5PP#Grh>JtF5^1YOMIQ+B(u;YP5{)DFHpkv!27X=%S+3mX z+Y4jKxqnK(nbsuz8!?#mnx@vE0?#yui|6|szVFtjUY!*cp{N7m!qbw|umir414Mt- zDg*DrE0<*%%rD!eM_tYD?)j|=hYad6h4Kgwm&NmZo6@F?o30;xUw=PvJk-Q?Xb0>2 zjQS|vY&=Xn_mpB>p+B;i3s&4uhntfPm0e>lgvJwX_}RbRi)}g6ub*W!Oxh7+5_Pg0 zfEIQ;r#qGw_S28Nc}<#ckK52MxpSiAvoe}~7?+#SUr~7F>n$6a%CxUgU1ncK4pH z&aTIY+IJrpFQJe^r@3ohq`eY#8)nTJM&6O)y&xN)$6w3U*vGAA?yj!V6n`##j^ z&78KTqINurft@Bo@;r}+FT#u{Gm#Xf=ur}+(x;`{DU{Es{+{^GlwCAb?`Vz&TDa`dM}KBCnd>5Wdn>MtCLs9cVbCq^ zK@V%5lMce^T%{#oNud3^6Isvna4R&X7|WLh7sebG)YnXoo2-j}mnEIURUKpvB+RLs z<;Ol_LhOQSf@s7-sig<|6T?HONl^%<|5{<~N;GPA>6#ZP2&n~ThTI0Y9b0ELWlopm z>S^HXF)otT8RcFm(weIVpnVr`Uk{z)v6V%-9@Bho%ma7}T>WMJXn(@Qvy>*{VCnh^ z82l7{i31snSKShk$~^7X30#|YXzvo=L1v8uQsRk?Y0V=GrVovhKsK;mN7n}uE; zZuR4KG|p`*tdXQFd>k*?<13S+?TAG4BX<7UFeW}a4 ziSe#};F;mvj*ew>-4fR~NG|B5_Y&7_tXywW9HY-@rxH2Za~)=TMA6Fy9oxmQ(?f`4 zWXL;$Y-HfEzLH&yYOkVha$*7RS|T7XiQgUz+tbVBhyLeE!6nxamm3Wa48vZ)ev|=S4S102PW61-?M3p|B`-Zpl6{SjCkIFoI zijTJ3KJbynVYb7vqms1_ne65;j&J_DWaCh}V<|x~>7r@|FOtI~J(hdnso0!#v!-E}&-GJ;dTva?_?_pJEmmjNVF^WF!T{G_?&pomEf@aTUbHpMLA2$j&Vzv1oCoi=E+*jrzmgBx0eW-8d%+M1(%IT zcR~2|;A{}P8;r0;xV;pY2;W*Fd5Ups?=e~8ApX^BiKgFZ=2L4J0?_p{tO1Hg*+PV4 z7GL05xdP}zR?giAksK?War5S1k5sw}AQkeV*9&f9?FxI58hY(P4BKt^1Yc4i0$@Bq zO8y`v-#7AA&hg5;P>!piCPzuvsgOwsKDTRChGJ-RowHtg9LL&}8s9($nwoDd%N(t* z7>Rc}mG8TGTsDwzpEkL;r+G}@^O7M=!2s|xfRX-}urd&Um{w#*I^1~I7?x{bPrF^S`wPo!_}l7PMTw7Vu+ z-#53XPPZTN_pH62wy6e2P6V5 ziOaj`L}?4G$xuvpXjE;ZM>uzUa|W)hiYxd_n+ifnD(sCi>nVpKzt_t@rI~JTxBl-i%WmxY$bf7=foaEzJ9A1l+Eq4M@;mp?3-P>GXhOK(4sAsZg+sv0x1nJsas~a!qa13tDfJ zns4ok3#~afF;Q*5J{e}C)W;cbND@inuZWudME_D_iD*r-BLA(;N=y$4jKVu#JYXj# zxz>K|4emSetrRNxU=F@omYR*^gpt9)%0V@`a&?FYIQg=|LG=_ zj=PS!yTdSFKKjbstRK9#vf9ph7Cn(X9wXbAk=;I0ZE>(;l3kF|ox(byEG?xD z7z5Ge1k0P!iPCEu8JBJNVy$|V#Xii%0IrmK^wQovF3>K}kNdjBhni1*q+8-BKgdK7 z%=ec4Er^Nt;`O^fApEBDsQO3@)$*fOo~WCFo_-Hl^Z=1>tLWpf%yN>se_F(s`3?`( zxsQ7<6wci>oU<6B`_O0e)vedUR3`_`X%`f~oPbtC@s{Blm;b@E3_I}M zBVAmQ35EqnkxqvN1xa ziE`>WR{WbAP0ro}CdEUjoXWefe2tpXzY^`erbgvi8e)VWU8lgqL>W`1ScZ-v_@7x8 zl5MH_@xrQ|54jaB7gT3T%Tl_t)g>{!ne>BXq(!;7>M&d02g~XsxD$VjGH5tLAK3$kBsyFSc7(`NZ+c$a=aWcvl^XvSlGPCe*uSWQ-{9 zs^PcNPJ*A+s;_4_rZ%fQg1WSIhZai(d(73m6IO}L~d$Qh4@PATb!4Z z*}xc}>ns!`o{B56@Kn1ZE0@=*#bnVBPI292=4dfrLWdjV8Z%a;S^E1zDZnaNp*SBx z0$Z&oek0u~a10*+vgQ=kB~L3To^-Nx5B7eHm_N7o%|+MwEK_Kxw~$YGNyHL3qq8ha zhAt3HBlMCT)P$82%wuaUv*c`u_wWX%w`3*e@o^pRX;pKDVc7ek7e1cW)FI#foct@S z;uVB(cu-18Sb-2W8Nvb;L?F4;Sp5cCSMginm8pb$H91OS^lj$Y#+Zf_O#5VO8T(AN z8OQKaeW)`XCZO@&+m7yz$+()5%lUl$Q+nTv19+gT%p_esxul8L`&e=*Jtf z3g|2=^^jIKeGtn;RaBa$zlg5EOnI=4(Ss}Nr+Bkl)}jK^ANdfBd@Jgb-7lY4llsV5D{P>PNv*}w8xPY?Q@ngx!1+3uu<{ZMsSm#`=P?}MY7|Sg;-EKs7 zD#%IS*_mF%-;o2_7jm>?)k?9xQfrI_L_OAtzx|skwkCvJC}uLtQS!rQ7hSQt^6j5G zNp?3lylLjuOf-C>W+s$r6Xk4wG{Hd>w?5GHtCqC*PiScPJqP&hX;Uxw%`TnIHAcf2 zZj*r0@x_95dvme@pHlZzx#cJD)9*A|4Bd)5KB-hi)B`^e#wxg@s%;O1Ytq1n$G?y7 zv+wR^$T0IX(H_B1oTe4WIOstYYvr?x$T#Ge<&!CXN(3IjvJJFlPRmrq5-ouw+++IA zt&(UpFJfVd=|ugFQg13~qYq+>(m;-W^53)=ReIM2I;)@;bxAz)gJ3yXy^lzG+RM-H z9e(6$;{mM{L@kGnkq=K@;(t05^LeLpPsI?lPg$n!b}oKtFSzoP0*>1a41Lc>UArw@ zp(rYIT#^K;_oTfokpm0P!F#XMDcTZTi*vGS}g#)dD+Ofz0@ zK5VJ#CCo>QlY2LlGLM_ACXl|xywhF7s&haVk@s}PhQC-cbDS4*nZY)9%*O@rEfH?T zPedp-zx=9eWWCGyf=+(OvO6Rpmz(FlLr}kn%y(6mCzCc5_<8tg#MIIqZYgnh&9%O# z#FRkAfj5+h4g1D8asH{2AxuGtD3px|m;w-&{;)8%b&-n$L@(B~kts-uH>RV~;}&0< zZ+CQs2J_@JQn9f>Bcbo`vJi+j*V>f~uZC~iMvUvUvZNc{i}dYr1~jR`7qTbz1}20= z)wxW~!J^hN(5b1yvIEB?-Cr-ICv!QcRgDE_#CV4NiOT7_?Y}1VLv_Tv0U5RSb1!c zv{DE+%#JB+N%(v z4YQbAE4YvqmZH`)_}0=^h=u}Wf{S2MRjX;iNe+$-V=W2BVR>&VPC72(45ow|53frk zvuxhBdH=Js{qD3mVPfqKvhLl??M0&15t1E|jSLGeaF(mDy?cIR1y#)^@O~ftPW~<) zKEd&9nY<(wqPe|r*38Kfb3xi%Oe-S5uU@Tft&TwQlRt4QeUjyD#BG-3%Ju`X!8 zB0_){4x$c6RmdHxaTZ1b!y&vX(m}Z&QcAq5g9e+#FWmAcQrbh|B*9f{f@Sn71;H~1 zPT+l|kL+#%BT@#(muNKoyX;pREKP*1n>MTZ0xXG?5T9SIUwW5Y$GHw+dUfGEQmNlI z)mQ-8Q_9Ktxxk377NfRj>;A$AyUaeEY|kXHiHZ$)*NJ6}m_5!%A%vj<=Ijw=)i;~B z9f?z$tBO*gJAH(~q<4SnVaC%22UR6#OxpNG(u2R$N6$2qZ6RKS|JAa~!#vP`ow=DbcF@B~$WQYL4G| z9t|!28s>dFtTC#UVdf_(`p=beY)ii;rP$%A#44ZLuZ_9`bl(z{Vv{$&I`)Gd-`(?; z2VXrrNC=?!u6Mi<--8W2%?OQIYwGYWTIGv*Rm)MT*i}d;2^QykC)51m_9d5%%G%EY z9Lo%Y6JCy-&3+#8>Kjg>C3{ZALz8%pLa@vA{x)&^kh;mcEeXWFO2ZX~$?{GM9V5hmlW*wIye909zx!xp1cQ9n#y^`r>!Mz|=lg?>Ex?tz6&QCuql>%?545ua0{R zKL=mA-4Vvj-n8$0mjcGx_g&9F^LzI`e*9wJ*Mx^P!Ng(DwYd7z$LR4f<@BTTBK7X0 z=Y7oH5RM;Utg?>$cdf32jVBdww&QF@bR!w^Vi7h4+qkmtyI~S`v56K6vFE(_=zL|? z-3mSB*thkSQVDQ!go&rF3(4aw+%IOoOiAEuRi7ubap_HFIG#ptSST1k<;T4(TxTvd z9MLn|WNL9hzvfqHn{Dz6b-8}VhuP6*teSg&^5Yi#85_8o&7ol+TCdf`I$b};n^gKP zklp4cTRHxD!8vsQufrsmo-n5Mn8@?Yql@*uXHv9!8!Mw4Vbf?KE2$u>`IxA(dY?1u z39IH}L{m1wm~JMdOE`Kn5r<7O;6eSjGobbn?Rw~K-~X4PdQ;FaemkB{ih%cpeC8m& zD?lfwYw^#OD-wF{>T?%Parhql)txp(2HL*th$2^}&yP!j*ss}7vN9L+_*^=Oeez`2 zMZUHi;<8AT!Y41y^;lkkRP%KTt)a9ZCiTl=eH3l3GRpb1(^i_tFhr{~06m$;&llzs zzE0eQJlWTgcMkg>tmMTCEdkv7p#Vk%BSYE@kI?i|E&2>iNs~XTmgqj|PtNBY`WEKotQ(99Vk}Qi&RxR=@AsWkF#ntz4`uz-e8o z%qSh?Jyk`}n|AC?J3iPOuoR0RJfBSM(q%#1DSffKnK6u0wRJXkH~k%bxQc+Z;$6j= z_FR~&s^LT)vd6MVYLuSWK>$7niIqDFzcSC;`P~tiV_eQcU7oX8q5E#@CdZQOc zIvPIn*2E2FE@48Zo9{WY2D=If-whh zJR~`_NQ$TyE$c=#u9*myB@7HA`%4(gl)~P#XY*`>jYRY}Q!}tjsK{gWe15mojcM(u zuJI?`XRdegSyUW$PhWOi&z(O$ie+bIX*uIrv0$5>Bak@}F#BmvbT9arhdaSF-*axu zmMq7ck=}O2)jFqS3==)8Gc#rggu;>@Hx1^plI$uds`j*FrUo{`0Q!u2PLACJfCx*c z+T?}80JDh$I$|njDSwklr#&V!kboa)(a-c%IO>IBb%E4MEJzAR$6|&|xnPP@nlavm ziq#Xs_)|!+1~>I?RtBcK3Wc$P`&@tMi-T7A=-7ZCy_*fr<^I#E0NP?=^K5hL^a_@A z?WhVLB}C;Ut`UgkcBbGr89il}x_iH+vThP|lgr=pg5q#*Mt0vA@}s zFEzf5_I=D0>^^yivG1Pea_O|f_=}+}@%8nb2mc7?8>xZ+2cP9N94UsM%WG)24On8U z9EFVp$E%-Gb?h}B0r&-*!Bwj8-l-SkS2UV8I}*)Ee;RI}O@l8k{9Yctfqy>#eeeeE z_iXsjj8Bm%VCn+Q1k!eXr4Ig(l<31Kv`geOm5?-!ZtRcrQTVQuGEBcc1H0`Edr-dJ zg9vC4AVx0;q0#b9?Dqjo{PCqizYxa!`{4;DPkKGxe4T3Z$a#rApp+Pe$ba5+elq0v zCtUJ7O0;)LXYYibs!=t?)OqK35`_;AIuWSC-X?d8?}p_|La0UDLT8B5Bm#rM?F|O{AlKD@^_mYGlDHTD!_~na)K%v4dQU%s(F2K@cK#taHZP zSzypu0GzB0!c-X{)fqDML%WqdOlIrha}QZZ{pJyKdLk8q+r9j)e9Yf7`r^QA)=jn0 z9m~KxNy=&_BI}u$tiZ^0lYqJT$u0|@k*NYk1w_&W5!nwFK)ZD))+y5V>$ZI>qnMqujpU-s0}6pF>oPPP%exsY?aDYSTE zfOW7H))u1-*qP|GbsjZ%Ao%X#)-0U-BE3e01P zdAz(KK%HfoN5DtllH|71@{}`1&zejM*(PUT)~BvfJ!kBC-Z1Q( zFAe}o2m4j$?*3BGnSxvb)}7`7u+KSX&RSR&Xvz-@P;EJ=>U)z|f}~bp8?DGnIwWfV zXB(`~7zvmiz`?=LuPWzO3`)St0C9GnSKAnpxdv?n2=}j(CV97B!!;g66tktST4$yMRvx%Sk2;Q#dNCPnTu$n|09!6`4#(LaaLl zKXva9rmUBV&3siL*d)$8S@K-gjP85Z59>x{nd6(PjJh?sB_Yy1ucJ^#W^X5fS$ran z%oABikhB7NW;tKdBwT)2o^}!hhX(YfC^To+C=hSt^PHb5gC22)>-y25ayZ~te*gJv z3b;JEDdGbR6S7s8lOi6##jYF_I@yCffD|!*HIwsefKNa(yDA^JE8KDHLSRO%rlZ^!Q3FD{iCtWrH6BiTUDtY7=y{{S zwcZsD7$X+$i?%su^qyyp;>-7VU(^9Y^!%V+33$Q%2D{WjY+?7rBoIy-AYxHtj7(hO zoaY*2sH{(!>~qT(W=v5cz;lZMy7`>2dZ7rL7(-P8?fM1h+Zpc-Fo0U1*cuRHuowI6 zB}W9TH3mZ*56kR4o>^L5lw^XlM&FBSvA&{c#AEcZhupW0kvhc_&NbmaL?6Fa(Kbg2XcmZnX)5xc?B(%m8&1 z0MRkH=RMdHCZ6H%*qBGPHeq4&k%@0OZ#157*tdJY%vN$GxorU4KUH6=Y=9j4E@YjtT(Luo;JW&EjBwybQS!Z z{(*o`8;S=9>qX;IwdqI!l4q6|iBA`ee=ry7r{cc!rm(gGSW>5O$V z1pw9c(&FrHRW793xHfC1*cuA8d9k`tBWMilkK>2Od$C@Yc&}6nHX6mY7$ZomVb?SJ zlQm;w&FH$B0qzXC4Px$!VGGu3b;+YTIxjV)sx~?>p_N>w(%xupOR~@YY({38STtZo z88o3NN4!#bC)a1+OXut@pkhN#E=O|QnlZQo7XdJ7>gc1nqt5*kswCebW%!1or8 z)f!yqrC6$(0+w2r9Mpzl%RUni-xJ7gmF&Qo!nra6lppo@UGyt^#QGT4cw3hdc(N6sneUgH-%;5`OY?r}fLHP%Ruy}!J@B+)Q{Is0C787!bCX4XcSerE1aCE6BHhu z>M6yfbH3)KP`j6lYL7dEI1JK8aO;(X8PPQw7?LxskUM4Yad+HY$Zg?`+!o&vca>zH z^?Xntv(``+{EGQO_`qT;`<$ky&n1M4BB#J-(k(Aj9*JSrF=`#ixOGGfr@o|D6a~8S z?i?04x!WJ&1!L959`%~0t(z2;#u=N zo3gj{PSLN#wq#_##MuK+RPM>GM*d}kZ)_|s$ct=1UMui9`3ihKKGUyX|IR1y75Mx+ zAJl)Bzeb?rA-N2ZtcL*2!pRI(W>P^et{}q!GjHal3dm4@n*@@FBpFM@O$v<1=RXa6 zMqy$BMEJO19Dq4vz|SH7JmAmjXEmA-yr}mK0g6Y13j#E+2ENeL6_gkw6}SeFI6o-M z8AHEzi1fKyVYHF#Lv!~;SHz2lo8!pPp7QCwPHQ zJnGZu5}y?&oV72`2_Be8dao;UP^k{<@*K^^d9x=CSOd2B>LN?d?>JY(~) zKkD-S*Bm23uDRur@-gD2$A7A z>3$niOj3#8ki@SK(|Vu-;A;%)c8ra}gyA*}jEoJBD9+DHjI)gnc+D$>QSoM8h|LLYY9hoQoX(cwV$Z0;rQrV!FX^%2GP z)(rLHopDC$-K4%(5u~sQJg<`M8Rrmo=|2s8axaqL1t0dl?)MS)z)d zv4H`2Ww>D6P!{=R-z!*9JU%gdzf!Eo)yOG#3pu%m@oftLjIpm106NOHTEO2|eXG3c zT?O0G#~I=Se0Os9Oz>qJAi?Z2?nt1!RQ1^U#?$gs*3guMa&p&18rr}^jlvk`caig3 z$4CL9f6+_9+mPDkXj}n69z@o^5W?Dq5k_uq<_TaYsD(Z)cs>FY6epC)HU(%I0S*qV zoB=SL`pB+b6kJ2j2{t^$3hc{X<3u`@L&IJ=C&@gj&g8HLi3Xp?0H8!CNVMg`PKheq%{Ezb|;7$8+WmaIjIXNnX#HbAX;YtV{rl9`VHh z@WjqMdZNoF#t3tFWQi{{##oGFRl<7oGlzcW(n^eDVhry6k%G{Gx;Fe4HO8<5PNq{1eAVKPoA^W5rxsT=Q6Do#fPq(a0FaBklrI3xDh(w49!3BgodqK$Oc2W? zV0Fj1cTFs#G0g%%2y0ej3yoY!jU1mLlgSX(&{ zlm(=Bu==Zepr1P^TG*7~!HooYvudr^?sDXUUhfhH)3JVkBm_BEV#%bCVMAwUpb)&LvE$EnaLyV_urmSnG za6wT3_B8-Y3()Ec{dNcd@{a&~Zg5dd06Zrn?ox*Ns%#6OXGBZY}E zQf@vc=jToRl%WOklAM+)9+fE`m0{MoxfAp*(sK>KI%uTsE+chcAa+}bKz@keHA;cU z#ogg%t!iWe79DI)gt9m_U{*XT3ViV*} zNC3v^f?7?bh;OiT$WmBMbFQf$w2S!K$>4C|4z-y$doZTxN+HQh4C@6PMd!jks8R%| zQT)*OsV0SU)ED`xG_@c<@-qA~aba@-D^K^m?*DL*1fOyv=;L`!~k9^#Vc}+%ltc&%P7cPhCq_%v*xeGSRefIOuob)eh>Hr zEO!ELKH38e@j@Ql=}_NX?8&$%Zc!BI2vX1v!e$Pur}&h=0~tpZLJFK;oHnJP;{u|A z4Ji&P4?D!70K}rTs6)1+P72l7hV-j)U;8M7+8zaqf_yCXrF?YpBV#SXIJaCce{~=zS&z~Lmgoriw z?4?l3S)~?LHb@`J&mB_a87?f#gjg#OqtV@0;PVf$4F22x5?_JO|F%>2H-B*l!IUOk zo)AeFo^2{cBVISm89Tt?lpt*eUEfW>yP}>ujBEhL#;Ezv1E1*Wy;M5AfR#?b&I!qg z`uGizCikfPb%!nzJUC!5KKw97LdYkAB-wT@8U-bkLDU8KEF$GCz9qrrYZDz{gHL-2 z9d8mOILYluDE9==Wm|IU^TAk#(i(=*suf;4Y(-@ycDgi4Lbh`Q>u6#Hyh5&L$BbId z;C%$5UII}U;1lQ>phSgHmS(*nd&k$ZPj#wLCt0K1=|k?KOZk3H*4L4ouEpgEo?MF&1vCMAs7ShDlXM$ACxWgUBqjGqYR7>nKR*UQ zgohsT*eHR_f}Q7pSIm9uoTM)=4cMQ%7i~sI3fFMP0FWb_kBm)~FVes$D20P9hmYbv=2)Ma*mNwf)DuUVJ#-MpOd!Gt8CZQj^nLPi+Kb(5b0pJ_Z&32Xt# zgCv#8+EZq$dTb5>mTuJ}l;{;)nE(@pCcduPyi1cz2LOm;cE|ea5#Z6)^XQ{AkM|6- zrO@|EVHlLk2v!a9L^a)xVs)U+jWCEq9+O7Zl=@}AX}9ViLF3$X9a{pDxFP^*;qso~ z`2IIuJ}QF;9W_H6%4h?b4y=ssB~2sLYy-cC8h+eTbnbJuz*l`uM_#)fM9q) zQ9*&L)+C5EMMyd`mGcH9PVyOJA4C8Sot#;aMNH-T?*~4S)*JRUl4;a%S}0Lj+Z5LE z{S7|A016$(Enb25?w||;2^j{ET*Hb=@H`Vp_57?Y^-vpft_?ZY^7)z^9@XXW7=wSL za6L-|kcVK>mVil{k+r8fmShoDtNPFCSsJEL)~6xFphFjOnr!r8|L9pZbcO(2Df|GE zEC7-O0In<)1O}WJDhF3@x;>JFJ>=*o#Vb4wVdt=FARYmfz|`el@^JU)-tZyW*SksY zC@sWI7t&_GxcNJaSPHD-?Y747jX9(LY2cH+iI-@Qy&q!lE0{Azl5Cp50bV`s`#Zb^ zee8K3d*6pwt&6+DP6Da`?*vI-{rIEV`wfa8HP|sVnB@(gqk?%so}H=8^AHKYl3 zP?Kr^!VtxpFaVqv`PK-(x4Qov{rQ`*!b+5P1)?5Gpg4GCj@DjzOjv$MAn zYsdlhoM9w6TlKC;GCVxXG0jy*H+pAau`t4)n{%eaeW8*$y(^+fYr_jL&|uH%IWxgG z?XpP07_lGo7j@Y`q405Bml`yEOMJ~p20aQe!SY>+JekDhM7G!?&m_V7?U1lOXDq>t zc1_U&5%)P!z+AR^#1XLk8GQ;?P8#VriDBK5C}#|D1^2HG7r>`wOfw6BPi-Lb?*~5T zY&KtF*{m0hOXIAWT-prdJNu0JzxB<4um=Cna&tHpXwx zU-ppZKLD>I#^8=Ph^9sziXAsYx42-N#@C)oT-AA53delDCG|2TpU?()1nm?YuZ&-E zt7}IAV@ECx01HqVO3-DV;X^55XFoV7%Ii6J$(>X=q8QJpUG=2acQT0IDLA<)ZY<#2 zt@~e#j*ojk!ug$mu#jZm>wBwx#bYir1SHfw-+S2mL*4g`G4ZUEBBtJFy7vP-mz&b} z?E9ZlVeLx^n~Ou(tw*&P%yt5xFZudi;FCOo+ziev!7)S_e6oA`d0Gt`lMad90?6@0 z#BY!iIOo&s*EFPsfRkvN=WpTp&+zuoe>j(mkFb*uim35C(uV0cWVmvhvWX3YvXY-f z>=K$X*Dy85tz!zo1z4ILd3)BOK`?hW&0QZ~8sDheE{@tRv9c*yeASgTy2kMIVeXfI zlDqO7Sfd5(m~LgiGLnNTcOh}YzvVSQ=H_p$q-K?)!}#>T&R$sBl++@OPuSdl;yJ7$ zb$^W24CC{HMwG{Do7eg+;}e#)gJzd53O_CiKWb40T^lAb>xK6tGcdg?R0A{i#bpv& zCN`w;CDvtMfzN--D)^uCreA^2|2cE{_(`-AsWudFzyMSbR2x%3t~3k_D_$})s?yfj zDv8UYY+|tX1Q24A12-@sDL|h5Pf6XmI1C`cVdWt3nqZ(lq|!MA)WF(GF7fSaVd_1? zV=_b~HOA!~r*VkOu9fZ{AvK^OfJEi(o>YI@mFl~Fz$A;5kR?`-$xe{e#_?8(Z@!e| z=2OWLK&!9XGV+@MLGgqxmE)c^qrhW^H@sxDx!^`tWQR1FaKMIj`>Et=APqamPq1zy zXqZtko!!w4q+XX@0D?V6z5)|O<HScIP%am?60MuU9$WRUaG)k zr)CW32B-|gR;i5i3*Zo0(g2Wg6kRo7bAl{CMbegL3@(4BLihup8Q>EQ>W7;}z|T!7 z-fzp+T1Z~dH0mX0e^{Cj;M@@4z@Q@uZj`RM6t7@ck?6oS8!aSBL{j(>QuXKeN^2t| zf*9BMQfA$}87->%?~a=C_7E?o1}rQtlF-&SfS-6bEj|LDc1b(?V!Z{#Xdd(XozOVP zE#1I|bp4w!ZjpJAz{j0;5_ixoo&dR8nV2!8kwFw6(&Q38Xbi`wC02Z6tvwPvWcEKD z+{pI_ck<(Y51+9$*?h4f>wH~IEl60wR4fC6RF7})wYecc?P4l^jnVoQfZ~eHxP&Pbq;V*es_-vsysY&Xrm z06-ih31fga1vIn^;}wqVmt8;zf_`mA6r>r%0?jl6fLNNudSNJf`8{t6dvbO3lUyIa z=d<%XJ61Wlam(3_pG%wsLuG9+RkY)?CB7nd=HJ1-+hDIfiMwS7&@W;kD5m+7%2@A7 zZjNE99x=}GX49^`zq}0aN#I8HQ@hy@kyrHqlkOGt9PsZ55)@QYyhLM?FZr5Tsdgio z)S=9l_2=ud@nT&zU#=Sy@(3VUh{OON0yBrV%w?A#vjrHT^Vpdo?+QH3BXb$WzQMyB zx|u^89b#K~K+lJHEK})jPO{A7GG2;+a$AePO5H&OamM7VfkCI|%q`tzVIAsO(`C(E za>nR6qYSeJWL>dDP-2`j9s*kD3?>EVO#hN~`TjsE=A7A+GT?P-7MF@ODRw^F#)}o% zex(Ce26MhZ(IE(PbH-ZfR14BkhGPTc`)L>tyQs90S1SuP!}`oDvS;z*h3vxs{G=c= z?)<%sPX#{bMF<5xJ+QZ-sWSby>^Jp(tD8mwEhM^kPS|r4R0csz_Cqvaf|Z(OMCpWS zA7i#g0mA~5|NOWp2OlnE|Ko*}kY9CSC*Qf2FpFQ3t*6+Oyu?P}`69nN=NwSF7jQ?v zcA7}ObkHR2ZUpzy$yw%#!7#_p03aw1%RY*Hha`)~(rk5vyvTT3 zsx0u+?yuS;XU@0*EQRfKkN(tjJXaONm)`8393;xb(0S9Mg@YypPGSc)-qW7s3ZE@O z9O0Iy%+(reLYZxPR_UoU(Bc&`ZylXZm!bm9c-OLAxtC4zr{%DNgwG){?h?!G1$*R_ zr0HA^FM9IkbjASao1yC1TH(Inta-VCIt7r8{TRkK*UDXZ%UyAUj8yN97WOn(qmW!v zjL;&l-WMrG3?NGH3B50rU3y(8Je)H}QaUSf&e*ssY|@1_=1TFIh%pk3bT((CjH}%9 zX$p!in5z@w3ym@EZtjRN`otLAgB0jyPM#0(g~l1$je3FjLSqasXNG0KJ*+VXiWUVv z_l~fBVboDxIF`1=zwtsA7&+Kp>tQ?b82gIPH{l!|>l(!i`I1H%)Vg5fQ2@rv*dGOS zwOO1VEBlO5+)Ai1`@Lj9|5QX$Yn9%vG7Bd`d$w_Nc})4y*v8fcz;6z$uDemK$w! z^F1-mTWL@R!GF>8hfD+<*I7^V6&lsI1KFQdWyy1R&v(G=XZ>{7?Rz2I?eiRzq+4K%@+lXVc zSO~0BaNSrU-q%9kkT~ETrZP>#jtkP<%gf~~03l*wicO@kMN6nw-(uS^>}z z1@`+9dk{M-y?_-lxUhp?a=qg)<7PwyK$=-a z0Ar&xD+*G?U$FHVFrguEbhIFU_(7TnD9Et6xVU5ABYD5VcigbE+4s8tgWOH?y}v~) zjl7;`rNxLW>s49D^FlkoIRy{h^JcLB`!J1%?E5kMUz>(%9-~9=ww_V5{7A2ceJPTg zC_{0#I7a#L$uoI;HlOqB?*X5DCKpA78S$5%XE(%a*LZKXVg9!8n{131q(S`FVq{b~ zxWrw9zj;i*Z${?Ba3bX|yx}vEla@Y0#vwi^$9Lp9`2?TN4H)cm$@O#Wmtt0O$?iFq z7D}5&N#1I{#W;Zas*OwMT>}6&DITVG$h#PeYnwEZ^vjZeCQAYG=38RR{11$u*o#Gz z4;(kAa(ro}5#JXEkPbk^=PCl9=tM z7y$w{pY7g^5t5C@#}88f@PV&;fPfAu;1yLEg8oGbM4s-*(o+JyCmgsKtf#$GNpbNy zV3*v&*zDD7Btk8`EY)LbKviyWWl{Sqm4OUlk$Lr7Z+R{y7@Po5Gz0p$ zHy`I!OyWcM`T=?_0DdMv0X|bs{5x+>rQg`YqjMkN(<7b06S)aI2l!khDH=<{aVrU@ zG7m>mD$}X5LdBh+E}rtsVr~KdFqpLuz^6@tPduuAI2+3MCnNdkWGq`xmSpE?R-W*6 z5mt^5Fe;$$1h`uzz^duM7gU)|T>yof2|d{D3z9(gAJ1lx=@Iw=xPW{ zX3Dz+d^C*0>PV`C7#?E@Bw>I%j~<@}_=Eunh^dTED;`OGO&H$gst%)499CA#f8V3?iCt52P4L9sT zytvqJPUhal*dXy!Am@1;;1gl>gaB#MDHwmYeSp6GLe~JG+J*bqmqr6>)J5f*eKkhQ zJ)rr-5DBr$bu~y((DM_b*stIEng29%#$gSm2z*F*QyJODGwKQRsB`Iuz2j#N^{dKw zE3l1f1kLp-V5FHtFC1NfVc350ED}BTeGE^sF#A5jJbEX97$pqybq*Po0?oe$S>|yC zfG;PhjZc8jMFr^eoPkZ#pM#jUW#*7`<_cC^L(dt;0A`~4+ND@Cde%U80f@LK;RC>E z10e3hjQT)h2F{rR%?8e8QHs`_VSQ~s$;i`ZOY-blhTpo!8od=CpRG^lA- z@-$mOjvK&ZH7MQ)whh=*EHP4eo(7EkoaB+B%oI@I?~}TJ0(|n`q+e$5FK6)4q{*CukxrPBjxiGH!@QilJ(W{NsX3HOPe$6o zH6+>;AU3JiZ${geP>~Um|!-6Nd#P%YSSui6_z#DMk zcRFkWD#38ShFSU!@QQJcl|Tc^QJ08Wtx5)V{8zb?=EI5PlB)m1{bU6Muy9V=^qd6r zSO%!pyXh4pI0gJ^pE76vRdXY?VfY&!pG%L7PdXB_|NE`t!`^4jTzBbcY&Cno*>gxJ z!=7jF2h(&GX3>O^9_rrr0-klwe}L)pvs_;sn`ROxSfZS?Juo&&O7W&rYXdL9X9Njl zas!`j0Ift4c`=@Nae&Xj42Dt@|FDZ)=~6(^rgm(5h85-~0EKqRcz7whX$tF(O^*bh zJ&&YXn{cVk0eSVm>}7oONKllqYX>N>a}T@mi1wuMrcI?}n=|bN^Q3ZA1yeAeGl}Lc z1*s+yS~E~5G_6s&@~uk8PcyG*ST^apxXno63ueCuoG?YYg6x-u@?+j2KV8ri0w(q9 zM#h{ode#I<^!?lye)i)SVAb3goHKf7Jl++zQvlkao-+VvdSAdu33E^AITHYQ?o}xO zeEc{+XU_3y#uK_a@XIw!Xb+P9C1Q+>#u&?~Ut)|1MXyJH#IBLm}0)i+wm z^{Z74U~Z2D`kt@#+Zo-mE%Akr)wPPe>_J=@EnX=H^YUh2ZaEID=WaKGCs%uR^XHB z2`4OCDh3Ldl~n+&ZL@9`vw#}RpUy{y*mQ8&gxy^?>!uA0w#j;FaKATMJ2osztPDGG zsW-qmLrkjdWR={GJLGrh(-fu4t1R^5I@~l z59A5}`|9i?g#_$0P)yxk8EXI(gY0?T`#B1E*00d^aE@1bOUnAMns-2(__K~+qzd~;GIiT ztzDCE(V*xQWe`OO49H}F_q&@;?wO~rI4@q(w3D+J0ze9n&_yo=p*D6w2R}>e@GXrg zFQfoih`pzr5g(`z{g-{|&sT7D&b(AxDjWHX`O4aU{I3=)mb7`9gR{X$liYOjp}7&U zV9u?;XP)~`n;W65U@d&icVsu;CcgrJet+CD)aDu00fkcQVF;yzTC_A}u)PIjo@PKD761Nz^w90LR1dCPz{2@W&kmeCL*Bs7B zk!l2t*F6Fi1wKjE)jZuxV};1-3)6i$LF1t;zqAGmYXoFolBrq0X{6U}e$MH66}D4Z z4vs5wi-9@XZkk|h8_(5sm@!WQ`=0auGgx4wt9{=4kbsts=c6S#8Ldir%4gK1FzF(x z#FJ9+3Tn;&-W}EXv(f;7b}SdQDOGE2JQTxA6j?X2*Ht9JtFYs;%NX5zU4+@sh&tF0mNkePN!hz~TjTQO2hMK9OdBUzjm0@}Xb@Y{9gCD=9Cw zknzKea1C1G8C~&rFXzoe&M<;UO4MR5?vcLrXEb90#EPzNnGw%nvQKc5CSlGnyfA9& z2e3>I2~f`R*al4G;x^8p>v%BxSHLI02L56}Sf;ZMzSnClBa(!DD%*#3SPASuW6sT{vyyzZn+G{7M9_+mT7JSN38Y>AuULLStnvVOuG>Nqdd zjePN*IUK?`^1v3;d5kgQOEkKmLcGWvMs2q;#D}YXhzBODg(@=Xi`zvh+^kC50*JKE z`#m-{TEs?xGe*xE>s<#1F2Ul%In!uU5a5i_vt|JMBbuXg>%%z{V-EF<2_ajv!N9sc z`9P5QMp_4Zaykeg%UqEQ3qUo@h8;fB%k6}`+DWiBulan}G*p^@wj;WiADxu=e6TWL zDD`I^*rh%aN?0QLUK<-UT;mxb8ILiP1#7we2Y^q4VJCp%TKUa%fd9lUp)O z@5pM7zc>}!bIhtt(VjMb0>nITh;}k>A$y!!c`q2THvGv}w_}_$30YpF zD6jz0X4ZyP2S^LM|NN&zx%de-9hL>P4o%z$nVZDZcQm3f4d%34E@!F>Kt=__F|?__@LG*L|=1U-$h@>41IA+1p^Pkyz-yuk^60V9$r~ z?~3L&kig-)X7)W6B@~iwihtqzenirDEQKmwaZS7^8+NI|sM7{X+H=aSb`G$>GIR@G z-2MjdR66yN)Fc5v13aT)<4`(zY)!UHJPsds?A+#JUhtUdzK7;H zW=zNs{uTTE@%KP~#(WV^Xi*hOw+-OaMdxKNXIlg?Wk=66Vu@|oiUDOP15kiU$~EeZ z0<5S4z`HV#8UW|*(1zTF=AB@lKxuxlF!s0psm{Xawo-+ew4MtMGoHH@}r03_%h@u^q1vF8t zv+o<6Gj)oS7q<)MzG#lqFj#W3MtrgMYE7~^?oeWk2r-6_qFf)lnASs#(Zp(|2J=?O zHGtyS=H>Y;m^dS_02uZVWB35Or^G@UUkm|)uiA6}@EU7v40AfUwIP}3fFFz`#XRkn zt&L+GfTi6Fd{57hqm|W~~f2Ek3aKT(AryYx8&}p5@G<(6B-*vr0@8fz>vn&@!Xo za@WQSwNRCVlZuIFT7b1v?lax13;4Y*Bh!2WDEW-I#yddHZ{nE)B>A^ewJb`-zAZII z3~Qqt03D$L>LL^twF3a?J;-<{HB2Y!DNMtek)2wiYhn$N|GH+DI8T+?9es#RI~Pc-AJ$Bx(VDLrtPyH7a?*IzLT7d> zv8kWV^=iuyj+Z z#(fKK-$P>H_vf(Sk{+#TEJ7l&an}=gA$Qd3sL)o5d&p8L4ugD?_rjVPacn0{y zBmdp8)PFvZ#v7XX&?t?%zf`!xYMN+$@|sbwp8HFx&!mDnNOl<(yZY=d$>z7q#HK5< zyg?IAiaD48iD%Hk?yD-LV~W7XdHnJ5ULNR@piTZ+;Wsd<8yvSYKbC z?+JVbK0n{H{Nwy2juibL$%?+_|DQi-;u6N2MZ2*vf>im}NmjnWm*a);C;1$}CjcRS zXgV|jZXKLR6*+0`JzYLwG?uw&DvWV3E~%|&l6&$>a?f7lmx)}0OD4gvllWYp!KmS) zX`dXD6nbIQWRMdt8&mfu-(+Oxo2+aj&-5c>F>EZ7p*xtR72c=Hs444@wtW?fjZH3# zdm4|>s+pluWICpkP{-(?7c^2NJo_&3S#09{qW*~hrX4(k?)ZM~{AZpc5786LAPdJ6 zlkUsO9Kj&+gf%Yw4T1&VghnS1jLa6&yWNv2o|ZLv%eV{ByZ}!o&(e;B0gKeb>m`-| z-}3B@yie@~kOs5>Y0o7q1`hfv+glE@Tcn$FRe*ivV>HvHu9UUa`bQo{KBq<8rKmnIWPDv3U=8)#+ zR$tnH|G)SPf2Lnjn2d3VU_(Bh~JorHRi`0V)6KRgAThk|`kF zuZ&Nc8&RPSAqxy5@zMnN`dHW5J0!$K$_*l@YT_ z06w{T&s_Y4#Eq&f4J7tUec6Ngx>p7q(%@wNKRjs?;?VtuB*dQuWYR(d`|Vpm?SulK z%)QD&w28snla`T$d_3p5c99$X&DT6CFr9??JN(nX>@^K8laQ{%qJ`-NOZeGZL|#$t z{+7A4-Rc}x0WL^HDmss4`N%vToZ)ZxFo#nryYBdsnZw8i(!ksrEQ9$RE>bOL0{I_O zV&q6rA|cY3{s7n3#``V{ztSxzNi&l#vK6jsheZ87wm(XO;*fCxQj`gq^Q*Plp^ z>iY82*T^lAzyY-CS>u8MqTrL6f4t3(hJHD+Y)Rho6gkPtShAvHRRPL`3^;-m2r2JLsCdyC2}2Vk9=PU2;Gix1%&Y(L(!HXGL( z_98j5Bw>z_^t{f>=IbR{f0mYH6u`^YlLhOYSYhFnzyB!JA9kg-cPf=OO^#TkCGMlP z-bswdyGa)=|7QMr+$-#t5MWM@hIOylE85XF!uj>jGd}Uz#FI?-z3%@jv+v(X`}8A> z))sqyW-Kd8NY|sAuO!C4kFPxkM2^cc_mdX6l<9s~{SWp%Qncz31*8speki+~f8}dH z-G|TP#9}5!(IoryPiCY!Uz5mtwHvaY$J+d$a|UCaMlkA!b^>GkxJ7~r@HzNTSsf3% z+!Q}d4dn5u{p-Fy&YgDjjb%x`pPG)%VHoiWe16MZ?Qj<^sI+u|%>qS&CQQsj{Lzj- z!u~A-d;-vSIXlKt0A*O1&j8$CGgd-0tmD4WBAJdobfZ7iT=KR7uNyz%4M2IlD|9bv zV)=Mqv;ZPM9AT@#Ib-(X!#Oj7J>mu+2y=c%xHDoaI}hi~RI)x~p~&g4PO&OEp;!i} zciIjUV=PI1fLG%Z4EAsMOpF{Xo`3~*DR7e=bwK-!xI*KZ51cb3bQ)uT(aIGvd0PIl zn7NDj+iHyx=b31Xp+0;Qe2z7~Q1;&qjN;B{0i^~FLa?XFJ)_9^65E&8oT-E;5sESj zw9@5~`01lu?$Z1aX8OhbOu#QuzgV%I!PX}CLVU~5Bmir3mF5~RD3HF0zy_RAB)y`b z%6ZYHx!Jpuw!G!c*uT(%8XfMDMhdJ7W} z>x$!Z;u+35jcJ;&+qIbICY}kQe9^`nFNkNp;cg3zo8~^7@O*ptSzg%}rR7?eE7z83 z43+jxC?e27x{oU7;te3(&w#r7a(X#5((2X`mp8J{}WaYl+kM{azQ=W!`A_Z+flicZQDQw2n0AJ0YBO!fVdR8c=vi7hoQ zrDko`ipHfDtDnY@-iNr9^Dg3qt?;IJV6Noz#eA3 z=(@4f+^V@tN9jG7n`f~5TeqwmVOXStO_75$-@B}xqyhC|iauw}e5)R~Gd#8*)=aBb zlzj?a?^rW$Svzh3jz0qMxdEs@CBB$3diZPSVIsaK#(0lpz0{--Y+t5#Kn}Z^C0W@> z%Cpy;FRv5a`3O`vi`*11M^`YH_w&TTCvyDm)Tj~K9NHcBM9)IQAp>Uf4cOLXw}=~G zugL~Qkfj_9O#^)DeM&RSpP0|@@kHf5tE@ToIwUWT4eBQwhq?~#E) zhgBAg(r4eL@=Hd;&pyPe}j{cXDBW%QI+sJGHzkr&y)#GJn3Wq zscP;C`|dUQ%&O;JY+VWo+)cgeN5}~6hr4OYI(SqP=-tZ+rS=vkmm#)YqR0s2@G+OM zu{USrGLsuv=Cz8vqr=>rgZbW{vG1LWJH)dTGL-30A^#P7|9KSkno(q&q4>ZHlQ`ww zsdgjhZYy&ie971E0-ve`tZ`P9WX9M(Wgp`)-e`b#0iIL zzcqorvLIK$+jji1*I@guz2sg(&5+>!ivnCniPZ+EmWn?Sr~Qo0+bPN-njd2CDOG+) zg$=*#OF>U9>Y#F@FNP0WFh5@FUiucel>NznQLeic+l(p15Gcct1W-5O8@t6hv=hZ< z4d8QvH}M$d!F{_e15)X`G@~WR)Eil0-w*shr}V6A$TG zXZM2i*EkS;8$ZcORO2iG5vQ*m5S~VrE3YTs6?r%F|5*ThUaB(aoat$GkH_yT%(!uY zg$*qwL;0ZcAk@Bjdd8f3S&>uJ?R{G#br;JI-|8P2^_26lLhW_N33}1$~ewna>0Vsf~Dw zK#({mLcM8jhJo-aDjq6w{@fb4W;;J1E!TbSLw-uTE;y$B_{GDML6Um%`n6G+a$`0! zJ7#Uu0akl((MNJFFlNdiOVDJ4f!vf`n$x-{uM`Q-v)aOIzKR;!O5_KZT&2c;I1bx> z>GFH-NQD;!2gbS9t;-6Z0hm(Z196Yri!G$hYV_nLWp4q$x!3q~LOS>i4H2zt3X(Iv zB;!s{?%c^4{k=igUe(oW`F`_wJC>~^d7fa`ZQH*u2^n!X$<+x(0I*j>a=G+WAWqU% zz;W_F(0Oy)&HIh|@3>32YNd~gw0=|nhRNLF2AYx`HbpBYd%Cagf`Y^t7A0EwVP^pS ztb35-HOwNCJLVDjBsgVH@n6~K)T5)&iO z5I$1zQAL6f_;Bo@?vwZWAmQ<*ZwC!AH=DYpVm4y$@wYpCsN3R&x-N3=me)0*dLRI= zLnl)cGQ6rC2zMS1xS%?>QZ03PCU@FPBL;MCS$3kl(ob7-{TT>?RFqO8I`OK^z)q2V z@`FxfaNym+%cbNfZRH0Q=SVgja-6O$^t^B5e;e5bqsAzREGK(Zt2Pz-so;acsMdBW zqzyE@3l-yU>5fq1EpW%4V$$QL2(O6Kwsu*oP6WE55JVyWsQcr+ z=V<(_IaU02`%bpc$WlLFt>~l&Dz0gZ9it#~jrhZOHXln1I+L)~zpBaYhu?tnA(3hD za@v-gCeFuY?HUPU0}@w_M9L@Ub)Ms64RV^G>)*7q?s981mcAZRn{ohbg~TpWO+3wc z-1CgJyD;FiYmUHoYaYx>aXD`LvnRSPrm>~damyEr$VpM|uF^W@HRw7Jq7DRKt7e@& z%?$IZ!YQQ)7OX0jM5CPea=_?3WBfRl0?&`?nXZ;ov=^*>pNn$+8J>#g6ASUKC}^Gq z=o|*3s>ei7Cq&!8LQ;=eRzdoS8}jda&1{7KDh*`(zQ7QRonLv&{Xv^v>aF32w->VUY~a{%Pt9oCd(&z^z`x9)r-y*bD-aWMTDJ?(xUkgS(D}i;@T?uIS84j=W#Xs8 zMv~7ZFd)nl3!$Y9T%i2!iK%#JM?h%1^3kh`=H@(QRZia*^?XrgbMgJ)VEXIGaO;@n zRqHbT1-XkU!KMrmfl9Wj;cwsZNOC?zqsM&ZCdm28^6X#gddp+JRa3kj)e zDngLKLQPw=`s2VEX5-`|&19Xq#(qB0Oe>PkK0a2td7M_3qSK>3-sL`i|FuLe*<+rAYf<_?i_FB1K^7TLQMi=pDbc4Qp`Gl>~ge^wV59vIE+l(33 z3NGH;cGxzsnBq?;%|{ta8H&P#JHlCRf_vVuR$dCMm0MsB6CHgB-yQ^)0)FnXT^!L` z@H*{kkf`*tf2=q2Ts3){v|eg>ch%eC_D56b30QPW4*bU%WFUHK-{KSY&sUu@h!S1r zONiXqj4~pIWrdYBd2gLz=dN*KrH{EHTGMG_TQL}h-&KmFT^a<%CL7Tzc$l+VWa>#aGj)KeJ zW_UK^bk`TpQ=!KZfS;Dx9vFkxxCG#M^OFDg{lkym+;!`-pOenI(hg%b^2YFzyG@53 z)QKzk;WS;Rj_sq{APoXORtYG6*5N`Z{?q0boJhTaYy1DxPJ2@F~2?izx` z;}So4Sq^wacm)F|qhBh6G_c|E%`pjHvj8wxd?}RHMuqv{W?+lLAy9on0B4Uf$vdUz_c{nW*nD*%T@P2fPuhS9|;IdJV z8gwT+!XasT#{e^w--L^j$GfH>)hIMST143EV4lzi>J|;*ZXx3~{N_Q18oaPOhQ##s z$ISFY`-#yuJorLJ!@DG1KE6q(k>=>RM7GBM!s$F7`WF+u+DeX|Pa6$tgrLhF)X&Tv$juVoG` z-GTbzP)rbT^uR|ScL&zy7*~Eg_7paG`#6+)X$E7i+xVqQSIQh+NOHE*|9kV2?gr>* zNJK%M;Q7}<@w6~+lfk{c^A3?A#C6vEz_Yd^g#7_#*YL#NC*SIx;%bh1*9^5 z;|W88MggF#pc#MHAlN==R@|qq56xA|%>q7T0E4wFv3_&JtIiv8qZ3 zzkfWspXxL|8o)P;#;5KP`wp)L?bC$%6*}hFZdJ6(`*&nD1ufOlUkr(};6vb3IW}kd zb~eV7wubZNnb_uVU-R?pi|mXZc!QRwI}q=S1GE5oOml2Hc&Vh|89~SfBnlouH)JaH_RUSNqs%a=mEqC?>B$u2;7QEEejpv%Elm~6`pY&E9(mJ>cz_eANmyPWOJJi;zxr7;&(oyGDzEH|g}`dSvH z+!JLYA2qa1nP;azD&^JDpRpDr!=jpGpr-BEW8`Zlux?3bb(Rx>O2zNHNP$A90hEdB z0RXu6e%}OOVOmq|km(Nezy+ufq8Bqhz!JY{WrXISZh2(&pmgokOa7g#?AjwCCkoOS zQ$lS2;eIKf@dfSy20$?a0^ByHG0qR(r(}4mWlJA>+vonfbkH^96HGNB5GLrysVl(sPXCZMSL)%)M)Ofx`KK#=HIPDE!@>i~fgm$GMd zsnmV!!$ScNGMpbX7AjNz50NN&Kn33B%@!$pzXn|$^~k9A3kZZXsd2us{85lSU1GJ( z>vqYUG+^KPH6YD4_{$`|oyqT7tk&7}XUvWOH293X*bSA150mTM__K)JTe&W?}_a4oXHFKvZGhdGgIUjDwMc7MOqp}A2%mExal`mhs}+$7`L)eqPKNPiWZ-L0f$t*Zr{Il&G-Wr^X21i_hC)AM@qwEyg z&?hOc7o@zov6^wrcgcP7bK&JMPQ68e>Vd)hdxaxJ$WDBj^0iTj19Uada*wR5vkn}& znSZnqJOXJY7A~xfla1UfZ6Ushcbbx0RGD5_jIWOfGuwLmZF3Evx2Y)lZ+Snifxo{QCA|FD9Ytb*>9eHF1N_KQ0LE|(wkLF>Q$cVqt9iHb~_}dqZ zjW;(r#H5KhZ>(v&!~;PicrAgH8%e&5t2!LKh3JZom>CZqL9o(5RQODnPY40t8gl5n8!YbTl$SdUKrO0-Cpp$v;nI#f`{2h_TKqJZ^OetaPD zllU7BNR^pQ<>{T`_hbINnsPgILSxKbF~0qqY4mU&PYH?CYw(Q6ZBqTb;_3_0*0fh3 zs_+Hf1~NNSMbQIsBY*(JTFu|1Wsw*p7mm&KR*3PF%i419S=WJUzcX*qv^j+lmDDn- zk`R7-gGM_pn)sWRs_EMM`(v!nOQg@mgNzXjGy+KU@t)oGIS+LcO+ly0Zfysfm7ck< z-RLT@!U3(wR1DySK_C5n(WZUO8 zROYX1%G(4T3K!Av_kdGv;4z%>yH9(_jwlO7t7{NcL*6SplJ~(M3p#j>SnmK(Zu$mU z-zjw~3i>kEtLEx$26K5|GmN*_`OEG9#Z(#AC{E7&EI029Jx9y5fBK3=G8TYtG=!og z6FjooA&dt08YhICpu%;}w_ea7U|#Tx5CB@P=0=(^tCQJiA#%YT9!PsmWtPJohm{_p zMuxjjkvs1YFq;|fWXs4wK%shvoY3hQ!?VL<0w0CRBy9k-bxZp zRZZdOsR@16a^SpEf}WjJEcesBR#k+JBot4z8_sCg$LR7lC=`19hg9-CEMxLI!-SR+ zcI4kZXw_Qq^Dd@4mtMND&p`A2Cv%!H^S$WB&8kJ3e6QQ9FDi#32thI}%X1L%zth#! z8Ky6y15@(GPuS?|j##_Q0p2*{-+^`Qd`01|-dw8LS}u57=eGCNVGj3r<|OmH^gjqM{(8Ki$=wmo$sY}Wi-uFOz- zZhvkYAzn0OvvBnX?kwghiMF8TJj|H*=$gmWUIIwztM0)#cMdHMh#OeK^<%hmqdq5H z{!a8+Hty#-3fPXdewQL|!#(EgQYqhQGvk)n%3(ERTNEIvCJq`L*_%o4N1%!2Jf=i< zZLD$8!!HAf^!r9HhUJ!T2-Oi1eR?Q8>(c2O><$6YES?l8e*X~r>fJc$?GJRRg4K_W zq0B>IlU18RUUZ8e85Qy|WGO3#2{Rz&;>LuS;M&q7&XAaf;GMYPbXhCXywir>fRa7V zTm9OvO7XxCi}8XXYIk^ZYgSTKXc~4*G(^t~4;|NhNKiT^I~lyiH0~G~mJxGfMp9hG zJP%uxPx?0G6x_L#`}7??QPJ2QyWs}>Gcqs(fn(3{XuHbOa^HaS<8IbZuVDxK?3A*O9&-<&77Q#3^F z$jnS}lUc1Q&N|zKLr0M#CDLcyZUop&VY96E-JQY0=alG}IYQ$LQ)xg*f#i=w%}k3Q z#Nz;Pg5_TbLYu$Px@M(&^@BM-4tiJ4|3rfFn>uG} z>|&$8NTV*PL&;-bK$R;{pcxo{HE&8oY`zZ`QU`0x5 zpS<{e89iR3mmTvb&TfdH*G$t~H_To~o{u;kcPU6u`nEc?KFeKLg2QMesPG38zDtIc z)jR`zLWM5BE=N$i)#YnR`)aWbk9+!4Gs)pMO(3x3u+^he+Yl2HSfKu}cXEG{dq1&+ z?9iG*$2jqa0Je>J4%Xoc)_Hk`H3|a(T=$7jAdMHXJZB5LxYPg4g6SvLqT#Knd{jU| z0?0MDGXLAKn7Jg*BwLbM#nrXfXn*E^yStLiZx1NCsjW7*f{3zMeYAPu3hUn{ESOu; z+^7l%sy_GMf2EPu#87MEQ3PSmP4D`6(yI$j7}LEeqtvU#xEC?zHI@-7iSD!J$) zTnabJRSL^|!W>o}M}Idi@Tr)>x#~ca(+d%XV~k->0yVNh5V&A(K*Z-~Y5bQ&i||a=nu-$r-ymeY|VbVH$g{2 zrWbl{8p;QSm{D~Jd7?r7yMc=D?8_@p`Fw4YnbFjbz_K391Rxg^fC6$~A>JO!Lo_J0 z8FALEO0#F*5y}mlbhLpE6}I0M@Gyz}@JpoYCX)_SOR89}(*91(AT5eOI8y3hq&(L= z)e8&H>2W+Gwngb@q106 zF-Ab&?|AG*xf0i40$5U%l=zoqwEUo1Y_M2K^jKMco*JKF z4?rJLlkp@lV77jEI*+TzT!4~nE{Qs_No#wF`QL_04+z{w6vyq?_NiE%sflu!J{uRflRjNiN0S0EGP zZ*R7Apjf+$a_#3(p7JU!;-lU-;n_lL`JYH)Oj*lngcA+NKN}Bj(}HAXr$wR-b7O{? zDD4jEl?XqPnRlW>aK1}s{#qYzmzqOcWO>AXGm>4PCF&o_b#UDYgw~|%)yV=qP{7w2 z`)$n|i)+>pvW+AU^KKZu(i342%!>rd#(vFmZluC?ZWN+B*}Z>iR-#pXajWQAh}IDw zopAgcPo>+XajNL>pID(ym7e_o0NNTuxaj&Z6E$Dsw6|$3H{XXGpIEm_L{VSlkbg%& zTRA&RSBYX`qh1}oDK+!%m=}!s*ZAF>GTx@Wd>?Tg=tS7{?gi0ivsaUJ6~fF zgmWoo&}08%K8q4{2!033Lrkk~iC^;?mJoLvARhByP3e_+d=q#t5Q7MgXsz)6p|(r_ zZ=)SVq-0oJSplJ`f-(1uerloa79RRFu!*kweiV@E^|>&ce0qxY2)t^dPE|BZC@ui) zT;ztQq^tCv#Bms|yZ+(OZUuh+ayih6ia22xA3Fr#ean#7#U#8yc|Nr77`ev978MIy~OC?8w#6HuXmn-FezWFv;G+Wf6pWY_&;0Jlz z1xbq^OoxBtqehV`bwgtxUS56GB)cB1Z)4IEdn}EUqcMAtottjFv*6mJU7BIgX_idJ zPHf{#1eDODlolu`ou@n(@yGZ6Nxg&q-^f6~8v<+Fu9%SBH4s#C!=&P5Lhs4n=T)JH z74B27)wNSssVtB@8^s2)yKmp_jUX+S=rrMc(Xf2Wu6_yGi)d!*e5_VXx=DMm8?k8} z+}O`Ywu{7r1-DEE3RjLC}}&;mi{ zi%bzZVPoHhWth*EJg`1~zThE&3N}cEuU_~}9$gEwE!d4_T>MpW+Ph3xw9{nRYOAS% z!I{f%Yv;dIAtzq1gCM)A?s~y65DE0q9HMbh#|svq$*FS#L+%3~rqggCVPS{~36a@M z0u=~~<2g5K>wbK`MUXC#YMF6QZ%o_q1;yPCM576^sT8&2w4Tpsd1uX&Po%w}LLrlC^o2{ay$A*vGtOKg+v zIr#Ybod!VCAMN*nSPciTp=ghIvretPW&qR7=w@1s_C?=!-gUC5y~rm^EmvrF=Odro z=2z&}m|m{6Q<(a>ME{SbsP<5sENPuz-&LitOE@KHMF)#&CIg~LZq{#)pfA|GjHr*E zuT|V_3%XA*rAoT9v!TX63vFkG*<)wW)J{$6Sy;;(){(IN91>GL^=QiRS}D>^*nX?f z*p|S_b2|lX5&&Npd@wX%t1Ox!#nYEnlafp;LBL}LR7 z{z#&6!F5R$sJG#Pm((b$ylUfDbrCsnIe%n5BFyh>&%8LfQMX}H(Z6>k#MFO-DEN)m zp5R3(0jazCc~?ZUi?CK|-wGYFKLR&&R1|fV*8#q<%zBjhO0S%z!{*+orH2X2^-8ZN zBgAk;i7Y~A*nhMOEgiKidAJ#)-piHD=~-r13chOuy9k zObQK9_zqQ04_%bBjhlr}cdN!?`ioDKLv=zN`{UgxEJ6Q%KJxrx0bUHLpHod{`~26&2VtV=4gtHIvi) zQF&G6n9cYQ9ADptxY!(`)YLZwtM)fVP9ssS?I9(%==fH*WSvybYm0WdNfsSwV#mPn zicfj?!B!O)<&>rlkNCAJ_UXGG%wDB@&UAi*dzyzh9{%A|_imAFRs(h-CT7a3cU1}t zPfUr~QR&*=PwX!MquAcS3m@adj<|k1J)G~di={Jc^G$4CxUtGp(ri`k?+k9Y+zf8L zr>jqq6#@?s$EaaA1z&wh*Iv`gr8fwAoMCp8xi5o(Ep8gM<=>xQGiePab?oW`7ux;l zJmd2PsNaz7RuSIeKWo%{J2i|!Jc2Q~foe^u@Y;;z#^jd0@Ha8A$hcY}+0r*JGX7Yz zaB;Bw*46dGfaZqI6XrFa?$K4RlECUoz=2oTOMH*L?bo)kx#=Qjozzyf^43FjC!^{( z&Pxtf1y!L>!>PZbAXGV;vkHjBpu!80P%}E45h}9rk1`tfj)XBVhE96s_rLQA?Tg=a znr7NVdW0d((7UnXQ=JW++Z-9PA0m!;-UM**&;*@%iH$VQao{usolLo(jQ;O=X0N>m ziRF0G!i#d=H}*sYbQw?2G1i^_9P==Te%T@w!uH9zYU6xkHL_Xz^43%@$sQ_*z%AnD zV8(~1UoS{yLiv>sz7RwvFz|yflxDe1BlElzX!U>Xop>Iu`A40K=OI44s&K5FC9iSS zmqyhf#?GC2{@ESw@Q1G@nN+#*&KmD1vB!LIH^b-aFeWZ^L!7w+E zwBjEOEr`)9S9s%ZaGRRoG#JnsYE8h4?djF2ycezd{jkUU5cLW(jm68Z1Mw*QY-JOT z57jdsgpLJn&!NtRKtTewpR%*EeY$KOdG}pydo%wY1s)Lm_{QCaWNkGd8jpp0cCq2sf=|dP3h+M}jVb zWmgcYxCyp7z3oKpQdPuoh$XlLV(PA@#6H{5Ck!+F#6EA96!{4|1lQJ}iEZ*9SyRvP z_^X;!)E=~`(>?lO&f;=-zK?Sh^qGtRK^SeMw(`)f_>113=Zi?-X&imE{rnT4w7y|p zAmabM`p_W@H5IVoOaeV^K6-qz&~L~ZYaVL*U3OtUCbuQ}9wWx#-!n)tgy9W8)aqlP znn`2yj54+4VxPd$*JF0HcomPf&_ruXe{~JzX(!MR`_eZdIP{d5MU>dLo{BDC{dc!c zgEX#4PMNN;av%mR zA2#5yc4Dd1$p7pbjQ|%4#z)R;-d@y*^V%!X6dVC9kC}p_@Z(hm!omLCLidEYX)mvfD<{Y^7W~mMmDe8y?1mR+NQox>>%2pu>_uSQ zF_^cM>!NOAWl%|KOJ#CJv9$$+(cubsCuB?6t3^HMqB2n1EBu?qQu<$RRrhTGt3yfmMOj3iwKRAkiX4T&+-rRC>YJi-}^HrA^-A7%@y>GDsKbcCY)^iOI6_l zMdZktCcW-$Jvu-5Z1N-N`^Y!xf6dFT9k{=^xwE#4q18R?ep;EOWY1ThKPH-78r%H( z0&hNh(Wcuhsy&Yu6q>3&Jude2!W9u~NGppBj5rlAU4@x)LSD(_M#b&rh zE*B}82l;yoaB47m*k))OaCQ9(71`>Mlz@1D)$omQ^#z<8abtHxyAB&Z|5mwGot2=3 zk^T$}5I5gH?vM^m{?a!LDW6@> z8ORWN$xHT{i!J&-p3(m;Im6o(r+V9OH+np4eP;HFvj&!rp^^ger_Yp(zairQeF_Q$5hxV43U*O7eV`CcURe0`E2 zssGDCWp*~cg}qfZ<}oo>dgju~pu7j2f#=7mm54CItk=y0Yqm@mPNh8Ygu8a7i;1kh zZ31LHzxNN-NURKdFif8}JNnOX59q;COr8-tmkc7x^V|CsE}~UV?)JV&r{@z)9b?fOV9TN2K zvpH~gP4+waPVEO9tIZUTE(ORg=j%M=;zBBOvw=04hvx@f{wzN^gM8~dsK1XFTf6I1 zWg&ml&~Hz!Vao?qO7Aa7=A)A;VbAKR5rg;5G7jGXG>B&-9rYtXot}vGROdhI)8}Vg ziC_c@fGhF4bC>H$E*R$hE3fUx&p~M`+3clRgvQTtISA^~V$|}WN1@vA&VrHRtRp3f zvTjwO)E)b?E`WqRQ62g>=C z4HAD$%u=>ickk(%ToH45rObQ``KA!RFv!AJ$4nVB)1Vq$eQMSP6E3D~Tv{nxN_15L zKpCh*Hh@N`x>L@jpF#6s%w;A~#d^uomGpKQ71^bg_GS5AGhcUxC(1`YCq3O=EmQve zcRlHQ!p2uRbS99!uhL(n3oX`CJB$K=pO7!vYN&Gm(m><@d8^02RGL0Zx)890gH#`$ zDHqoYk$@Gp@||5yu>!(@=51HF`-i!SXsQ#g`^@*aIc3q%*sWKU$rhyh!XFNf?!$Td zKEC(}-aeTkDPkE8_%AGr#{RISe(Fe0J{p;7_$_vvRmN@Gq__Ux^Mc1@8+a>7kN_0mT^T-Q_R0i8%M1@g7uzT}Ghf(T8{31k3PY{+VW@L-3hP_nIhDGz2>f z%{IACIF=lc$kAeiYY8fwu4ZS0j&l4QT$)C!dqzeJ__k|qufsBd*X#E4v@_+17{>5& zSzM&T(VX7QwfN0$${4PkYUf{vjk!03#cnMYnL@i`#E|&vkF1ZIX8v|XPxj1)16dFn z5Ns7h`o8YMISQ2#+0FW%E;jZ~ocN07_Obb{ZKc&e5mzsWA>oba1TpG8Rp+NC>|od* zrTP{h6!5{n%yXQ9E~e2~)-JniXDrC+4gw*!`J?Qx1(mD}_=_Hx-T6GLeVc_q3V@LD zstBt(*t=aNliC4D%+nhWfbRjgP@f}hQmqP!S#QC~(H5#@AvJB&*#xUGMl+S7C4;43 z6-=kfxN0ef+8iwCTbzuJYz@FkaJRB`sK(baFL?5HR8FHA3B}+ft$yz45h-?Ndlx(v zfX|&0%@t&h#-~(icwKwwOzEiN?@y@kf_1AOYgtE7PflySY(12GVF3mw}Duz_He;AJ;Q%j!sf z?n!p3wU-h5DTC2!&;iP;JNR3Rv$m|&CY}qm1KqaHIP(^#-BA>*78$#*t{5J~4E&>Qs9Q?v5S`I$2qszkg$qmry6oK%1>LI zkrN_NycMWKxWVV|!|^b`dfQd1BMqA!z&G)y_234djS26(wj2FC^ZX5g!@~Fh^+-@H zqZQ*_2zh;qj>_xIuQ&8PJ{22wR5jp-N0crJm@xuS^kIHKB;tf9Fez&Q`ZNHI(HFVh zH2!Y<6uVoiI;EbT#h>+!vlI7KN+=VThqN%hF_qV~p1(q=U=5IydeQrbd%0zrI=DJ%?3+m->Dq1QhsIRd%M)jXxNg zg}9fObZlPjLL z#@^3}jXkq;f*XF6CTQlpzE}FckjcRn-0v|fRZ8{L`o3Zk5KXT_&!WyN03KEfDHm9z zv3SCu*?m2(Xq`@-=F#(e*p5prRbC9~G{>MA$?2%KSe+#6DWWdRLRp_$Rg@V?d9*em z@VNT)0RTEvW>ID^_=9ua{DcMAUJP0B%f?$T3;xvyk}b#(N&2^j6E^LQ#aF4+XaMPE z|1hK$GGsoQd>BJ&7m}Dbgv$~-@|dwFw)+3Z~l#@njuK#QjRxrM>D{?A2;B8gTw zf2??qO26Fi=V`3@k{$cQr?y%JYx1L+B~KfN`Yq`ybykK1&_4!nShD*pBWRx_S4_^k zj*X8T|>PJ_YbLY!b6RCM+w0RHJhcfsl%s&;-D2PRP1$X z79ga<@9oc_N-vKL$ z5%RltF9$nXdS(=}+w{|DaNUU{d97p_MuSxAtpic{gB@kn0(ydmDEF_Ii&LCSTI)TU zcT@u{3#mHFRD4l!#o^qYpTEsIKM7Sd9=$6~0emsxEWN}GVyuZbpXd(2f;eWlP;Btw zkjpdB>svp)&7T3 z1Av;1m!2a9i*yh04VON5`$ooEtdJ6QClJ&TUxxWy)2%CI?ao+6`8N@G2J;A#)qmD+ zOjB##B-;6oS*}ra(X80GcM^z17C%^E@?IMDHEv=FsR=9MN2cYks5C_Rh9RWo68F^r zu-FSKM4c+$=vJK5RPsJf!@u@#CZQM#cs9;(i3o-9%prr^V;{U6)Hwo<#m%3>LyT=_ z+-o`GTr z+Ao9uSKKi z=Z_KLJcJ{J@h~8flg=lFvx7aWvX_s}9p_c_jZ`r^+*AP^ku`6K^r-K0JA@ZiDitJY z!!JU0alCipkN-)d>kD5=#x+bbH*gXGXzjkJQ8|CxIndY5bq-@7Ehyy9D-BsMTt|Uq zW{Bn5ipB+gNa~y;uJ2!{`Zv1(e;%EP33R^f?Q5G8aq*(+^p)2C?b$)U0}MF8A6?G- z0fH~`J|A5FU0YyUT^68OL>L&eL<+CuxpD|4n>~X@(GGiOUn)A7rP!|I7Uowvgo7wL z#I9^kq?LC8Z+b_GbNCQhopqyWD^54-vO%y9*m*YMcC2en%p~O=jL9K?(q9d|xjAu7 z7utGC5$UC>e5ky)fQ>yH7(l8}Sf9W~K zk=Na8lJz&}2xtcY76RCx>FCwSH69PHf@e}k{in3O zQNW8`!jd!ES#08a*dDnhsfCOzuiZW~D2M#aJGz`z8R~q?;)Xu*TOu)OXz6jtFf%YC znyxP?-y20U@LL%7NRYKs*CVN@A8fe409F(FD`ZS26h;Bt{&c1t8&A=*w&BpPq6Hjg(2M{IlT&$ zMsJRR9`G`BB_zmNV9QsIVT^#n;b%Ca2myaNQHs^tEAlRMZKgKk`B}ze;Nk1#jCLg7 zK9ra%u2psr4_$>MofChQ?>OY+CJn)R_DwmD2su~Lhqaj6XaeQ4b zq03`Pq&f1VF6jd4oYqNq6?E_PMyT~TPb|ggW#JPrd;0)oH~v^E-EdWC>lx>}&{9E_)`G6pJL>UQ3PApNd{dP7%%S8xEeP8a$wAiVZaK`Dq2OaPB*-Q~Otg-Sa6d8k zx^(9V40VPwBWdBDFQfZ|Q*ylg$2ts}_UOWWhID6bs>M-!ZV-gZrp5j1^9Z_XK5uM; zBTEG~|Di0K+^4#Kt?$_@NWXsJ&!;KNbNj0MMs5GgkxCEmbQi){t>;vB1tjJcX`7dwF3Zqxc^vm2^w|jgr#%eHof`v z+G^m0hm$TEZRqOcb^`ndwT66@LT~pK&{p`FG+5&$dLa=PPqLJr$46t%QXah1^^B4F zhk|s6$1sBEJwtDBj=*nknN)M2c@7=P%bwXa)R!ml3z5I50eew6#PX9g-8IWSf7UYTNM?6k7Dr zPBU2z5f=4vYp^`FCaLt^tCOAnE}@JhmaO7sA#%-ecMQexPj1>)b%< z8Sa!Wl2r1uML~^;LkUSDYsJvs5=CN#+X5$uok(j#$E#2{>wB}xqD{>HV$qy^ZJ>sJ zm;Ju=>embftz8XWF!6c$*bXfKnmMT4wW(2}%^j#j{DFsj^j%Q7{5^`4a5T8@1=sSd z&V*j4q$h|q%#=<%g}d=u^~0O(l?Ce~ zwoyi1{|Bu=Qok&cq*Pm(>~lW3hsLL`n=^hypRu~U%D(p_j?>!QNP}`ci^lrRy1z}& z!<)A;3N5hDtcwGS^8Q=PtVPZW-DljrJV^-73(kyEj`IR`+FR!6-cgPqak9^_-}HFn zpX@VwUfAg9&W@trq0h54=LIaS5p234kfEJ2`)FS#7RXMOBrDn7^q ze1-dTArAGgrAFdH`S zqngCQQFh|b{2-;wi5y?n@n!nFhXS8^OA4|~OvjMH({a3cc|tLn*+BpI0rMZ5k9dr3 z9$2#YdO2xcz5@8i&UfV~-<88_fX_Vd&)UV&!dG20=ght3pgc4dFsx}iT9#^q+HYli z>O58_K}#v^etLhJ zVA15tEH(p3h8zR)MSasn*nv?ONvDZ#S~)))&l&WNa~Ya_O#ISI}H3VsGQy*93 z6Lt5Qzb4M(6Zn}SzS9sNYRH7ulViX>G-6)q{;hG0PO-qk221QI@y1h`TUeB(h})1j zH1W|dzF_TthXyhlpXitFU8r9EP$kFmIykT?k>@1P{!A;5pHC}%GY#((T)&)b>lc<6~SP z8hJ@pV6IV_j`8m#w@ZQxK>dnJf>ZW}lLo+Qn^^QCR4ab;(YOWR(}v!s%0;Q!D0|nr zYG>|hd@%CP$N--J&_aT-xU47qMrLc9cuK22ZEaHOor07K1=%}ASNViwDC8LvceC=C zw8noL=$Vgipl5#@&{Iw2a{70`=SQC+U-TQ^0iR#=@cfQ1H+Aed-0XDrR)>yGe-5YJ zxO|*-IJ-E01eaDcp&XchZW)~sI?!S20EO7-2PVKim7)rq!w^tYotS!)gBz9vUKorz zuXKAH;JpQLdgmz-xLwBQ0ZlyiivfVos0@QkWS=*UV* ztrHm6t^v)^{kldgF<J?rH#~5oK?~X2Dn&wtA)GkXqM4cRB5z zXcMPHjldpuMtJ$5u~{6)g{V)KjfS5cz~_V#@*lBRlIQb}Gi}+=(9k0d@ChSn(8GD9 z1ML9E1pIE?bcxrSmxHnKr8LvM4I6ix&E12x8Akb981SNBIVE#>jv)LcW#CP$A?ZA9 zF7B~`5R3u<9R)S*IOTM))zVwLr6-PIQyj2XhB)#y%7@iDWE?#S7$%t;U-y9oGnkkgq6`!E5i~(EdGLKRb-p&&LuF%`7mT7|u$Mj)3!Jl)no zpO!M1L8B(T=F$o^UX{fy5AST!zK0|S7bA(qcw%h5?uj-Yy{0Wh1Rp!A(_8hFKJ27&pN)v+aD z0r_lolMVOaSYPcP%hC6gT+;aBJlBz|e@Tkbb!h`)Z{VOmUZ?C4fOi1^Q?=Ql{9-Z=}aG%QO1L-Uj$hXG8TeY6n!1pK@IyFlwTk&m=*qyH*EmVhhUt zZ338=j?tMsmVAM7Z4y{&*VPew7eOs&l!A6}5ovgV&MRD>Zg;GkTOuivUR;PvZ%OB}C(y z2tG}0kp>m9@Wh`{;PV>b<2nA!3p5{3%LuD=xMy&Y5Ld zWe->fSYKm~sT6=!!6AUp{%;08bslR8zUq&906hH?A*m9fAzLWQp6#}Y+d2?8^8z7s zX_wQ|DwAlg!0=^ml$z{afXzQ`I;QWZY zU)s37Brq?%_e-zQz&1@B0kPCC9TMb^1Ah*CzOwqHBeVer%`(aJA@ju}RX~%zJqnP_ zTv6Kul#G+G(y_;a!v7c}#RIf1rz zod$S~t6j-mp2@*g4WJvQ-z6+kfX`VfkmAT9B>_GcBQ9B?8fgvf>H+(4i}7pckE96b za8UyME7^?s^>gr(gY(N0i$x^vwMo)vMd=&*jURc>*JEjmn9K+w27oWnqzOEWc&U+GmZmvghVG>$)ry z&@Lnaa)o8E5$D{9DiAhJ&ZHJy+7Vx%+0_u%U#Sz8ayKf?;jBbhw;Qm%H@DYhc{$Gh zLgO#A{C!AkG>du3o+D|6UFQIFsH81oRrR0P*SdPn4TxXZXB=+UGiHV4FktWNRT@~G za|E_01c3iw zj&Acq6U!(VkH5yeH8IuAh0hcGq}Q(kpFJvVE)B@84WAC#M0c-rUU#L~a*7K;%@>G^ z&mYH+3DeD8{TcLJVWr!svU9Xa+2VH{Ky^RdJ(r`m7m_K|g<~2+Ygxf(vk#zo=?I~F zuV=>t<_(FY)p?^2nxtGl>d>s14SioXv8X2W>Euo|j*ki(EB;pL3Fi$;p2uB!tWimw9dW88y5zZ2{ zMDaJ&TvU5n*|%-@tSWtTlCH>cx{S6hRRIRG;84at>~$duG>9+i8YA)w;MoND)Ym)U z^S2E8-{@!VfX~0nU~1+`%|=j38M7Djk=5oEH_Dg+K4G8$$9#V#S z8>Q|C`2oPE9pDqd6~JZg-XoYa4<)fhc^27Y)zC}fgZhAfx(zz5)^VyERVi0$#;OQ} z*I}Bp+5eQ4(c{pukX^Dma9#mhhRtgjIHxiwolB#JmR-{+*`^!aE;l6*PVwLvdpvgH z@`MBz?!iX72gr~@Z;H;wWU;k@iM@4>1Na&v+fHD9iX(q~JtPATsgI@$eVV=W*y2!OY6U6MRj$d{#v zfPE1sx*cXLq@C&00OwyZot~=P_o*UTF-&V{U-!IVKses(3EfA z(d1~qBbxvcTifWGVX6-pw6tN?v`uEToWGXxHAc7HBEe;`Tr*ZCP~~ob0AS2moMc#W z9^3=*6PVf%g)d*1ja7>rY&aJk!Yj4lX9PY+&ahZQYhp#)$q`zW2lvn_yB|XUJxb{x zfcl^Xu!VEnX;5O8hbal$^SX#W6M;GNLqWnWz^4sx*NWkFBBfcD3guN50{t4#hIm-& z_y$z80qWoGHF8j*NoJdj^lvx@aY)3Z2XDHxcP5_-v6 zdjRO&{NAAY2Y^qQH6BKIi1z7-^3DpuWg2ii4M<)nvEI?9>HtRfd1JBydqE}1O>=lgIR|wnFNjeWm z*CG&XaXt;%bKO1y4!?@p1EP`6pPoCvoLLflKgGC7;H zb1{yso=s{^_EtTYlv&e9N3_`q;LL^qm_nZo__Vk@;v~5AuRf6g%$iwbA@0RgDs}co7*}{i-$XKm{19_Np>O%||uk1B?W%v>A z7^ibUjb@X%%w9L>HUM&AU9i8IGg;5V8;9Y@jt(Y!49J&+>3qI^4^2Zr0zEInuU z39;Wu8yOxwFE}eoy4RGeW}neBv4$q*bf4*2UE<@M2yk9_?AqL@@&3H1m0^9*9IT5C zojwEB7$4*QLmH3(3QT+q1wS3Vru6VF)X!+v#{NZxJ*`?NfC2%S`WOR%Vyn}EpMj6T zyd2g5dhjuZa}!fs?@D$ zB6q~^SmYY((OkznOTZqV5&sNT4Lp+w_ZOnXdPs7#atvU3LK61;6yHhYUBND=Bz8I@ z2fhscj2lK?C_M3J?!&63ggF+(pFxutem#yyMRwGD@B=nA?`Z{LvtQmpv*TW5x+wM zcA#%;L{pd=;9uBM?^|={cC~jG40aX6z)tC-bERa9gT&nKNSMP1jB`CQfo4Fwq|k0q$7Q ztQ^mqLGg_F@?0v+ne6$695Wv8uKPx>Q}^V^29-`5umcxY&?Y1q&u86>IN0w=beVDF zxmEF^XTm2@8mO-yI*2gazPR( z=cmb7a09sms!C>NJ|Xa_$)*o+RBQ#Qng>YM+Mracc>{ysT0c>}za$EhT2)!wZQ-kDv97mbl$Y5`z7&l|jQF z9m)oQMjijEVWS#FyJwTQ(l|yf5C@PZjtQrTWk_5taE9yJUxJa_N8h`Ze?vlI2hf!y zZyx@S{{ISu!?XjQk?&o9&*`GZq>A4c@O>P!hR})K)lPa8% z4&IN%!rZ;b>;u>`Ndm|XO95mdJVPmn%Grfdn%XZi@S(1KX!8?hY#)eKt!AY{Aiz_vl=oMNxHBLACY)=|_Spg?-Zyq*BC@$6>xev+ANmnLW-*w^lKx&r#)4 zRdZC!?{A)Ix~WTOMXZ$BI>O7cz7~`1^(f`UD(X+b0&~P;Av{CJvW_!UFBa&04LcA} zNwqLl7qY4zw2SD|O($xyW=_~bZc5)g{XC90%%W==n_L5Ev|#WMxPB_eXAV8I?{~}c zubrxVw^NhF`)jg7rhDbVx+D?^iIZptL*bBvHB&1}w~q5x1pp+2S71!?G%hG>hf81} zwXVelYXzO#Gh{__Pyjd021Wl-L08?mwU@ z7xv&Dj0*w?A50S;;Jb@?+hi=8IJiw5TxCY;e1&jAuu9^BVBO|`O~AaaDG+sVPKU{J zf_+#itb8>EKl$<7y)4Wviw2Oeg=R^5z`9=9UWfG!h>ml|EzwK|Cl97WmvQXXO48=F zP^@A&?f?>1Zb(5-9Y+N|$79%oaYIIyrbs%5c^JQCI$AaXJ=K(9n!07+6InBPSETD^ zVIClHQC87OPBE>xzWsDN`t^8KUCl|!Wcxw=?kHE>6f{< zPiAq>oB*Frm^_XVpj_qm1D`2^EDvDDh<#VtBh3nl02s7t6(XP}-9bD4ITAv11eeGs zgwWB$LQWihO7k_4SH{B8!KLh=OS-dr0cb{-cgu{l?Fkr|l&aqczMd0ium~~So3d^@&y}&M|nx}N}oyq}ZjZSZg z(#}5Xl|oD$SmXdEXOl0;{zP?xo=wEWut3nezh*2TH_N64y-j8c}NNP7CEny zH20ts{3-0U%?NYeM*2-1cD{2926w3hqo;@yPIZC-J_(Xo@vuE>bovFn#u}`pldBS% zxn83K-G9lhV?*z`=+Aif4=9Wwt&<96rYQyjbSEm?6+N z`wVA>g7mt_ba`LhZ>IZmW1FUbOGf{06+c6P51%HW*sCr07#jRHs`wXJLs|Hgn6?-aO;!zHSZQL_ z0+2Ogj0dG7{EI#Oj3ddBm^&|zFqM9oK`vlRf-tW_%iFM=1G3IRxQWfMuB?>TTL|j7 zK^paqs009bguOSkkuZt`7>fW57$1;r%b0!80I)oj{2}^-hm;?mQ+-0Czf=5~Gzo!` z9e*Z>uY^w%3WqowSByUsA_*Q3`%UATdHfkKV0#CDrd>S1&$y&<5Wpn9%mv@O41;^> z&$I!Mo1h;J?zsUlsPZiDFK^9z{>AYnhx*@U|&A@UdjN+*B2LNe>uYU)TEduetvrC8X22ENdf?$G|If0 zTcA3DYM@p2Ej1@QpM^PN!+-1H|21ncdCk1RKLyojDe%c_A7-8fr^bMvRCo09Z<#Z1 zX=--@tB}Nj&Y2Po;*d89GFNoYM3-PC!}MQg?QN2LNU$I4T+unBCHr*-2DKpO4s#Ol z-yiXtdE+I4;pTqLydf#qygr%C8M>7NLY@~oLa`S{iGOTG{L5&`Vza3wn708M715-K z&vTL-V=~~RkeH!bsth<^ecl2n z2gFV+ubMbw0aO@bvq?H?@_4{r*{b0a6^r=5G$#Wv#|;LAchJ<7y&2%s8$=2VvlAP1 zV23uSLTR3*rNaJKvU3jFUk!AN>H1gpeuFqh69Vd|ezxmZflrFc+f*P_IgjcjGGMEplEgTx!i2T> zBocw)Pr^7Fi;5pm-tV9}pEi1@604Pmjb1#$&pVMF7K3UeYrl&k0Jm;)Q=9GqR!A~l zJv%f>fP_GtWXNj7DjV36#@AF04v|qC!u%xj$Jq~&1?!O9x;!|M!{-NbNJ)JfLJM0C zO3nE6%){F}<^S-QF`umS5OlBrjnd+CF?qm#@RTI=5{*2SU8~ijdUhOh&b~S>%j@Hk zWJ%Ufub+2y*9rlZd(%B%S)CD&gP72UX@vrdf@B{-6fz}L00mqUWzI%8%k0B8jB0!- zgH5yy38=t&R5H+rSXuC!$vUv-5+{=s$fqF;LBy$YepdiKs~wdg`1c8Xr|~%*slVat z9q{>g8PsvfjRjCbosBrUZLdfI7m(8D-VhpJY?0nUcxA{UnSFoqV+>@5SIbc!qv4e(jF z#l)vf8o&j8gl_Ab#cnO1jXuXb|}qKrey~w6E_qlCuJfy z4bZ4yRh&wfvW{+Um%ynehxGv6MFDmP1SMpb=83YBIF@=ydcz5fjXB8132Q6=*MAt!A zK_}LvEb#EMN(n%fk{q;CaA-dn@QGH?_d9v{cBdfUzA4De(h}ui%MvFmI+ut^9H(*) zkbahf%*r8VgAruGI*ehh-hlDYfOXuVM1P{ENco}-(9lbVO)p~e!xr}hxRX7kG>9_T za?@cLcvS!%@8<+Q>Hey$B{i#i1|1=qfo-K^Wn%$tt{|m*j45SG5L$4I8-M~e#+Lg= zqp7285Hh|!wA8EsB{pmbLD~ur)CtI##7$*tg2Q)L|$oGZDIUd6rWq5VKJ5uEeTr10J0Kns=nTLllbkL}* zhHhRK-OTLaC9LKyW0=C0*stiAdE3Az!cAjIjTi}*1%e(ruil4|e2<`n0h%W$(7;5Q zpFK)md({H#9ro?eFV&$BC)H2TO45maNX@UTd-fT(9mfrhK)?3;ySgtZ@M+aVj+O|s z4-;h7Npz?+Npf@uZ2BI6NKY6h2MHR62D}v@9gVu`70DQt#yFP~bXMO%KUYf^``iH< zgVu<##42o-O>{w-o5>_hPR@ilEGV@;4BSBzV;}7^7rPEWY%{+MU^jUAUS7azH*9TX zE!1EL{Vb#?DE0iVc? zSb}q6gAwVR1r(V}(wRIW+o`Al4%qGh0Q!LaNS{yz&`=N?d5aeGM`Ya0y3kU01$gZJ zoY;ETCK2`K%3+>&APr%`brO<>2D2m~m#o%j*p zAtdH#+D1wDfXaj#47ZG_GSI{vY@}+2VLHdINMzA1mzu%rD*A(*hY$yET>9DLeZ~U= zM9+)&`wZZ#GCtqUg&;PV~?p~HKa0{oQ2Bx>~dzPtj2hsX^V?;(nh}U zexJ#wNgxu&R`Cm(cFvV>T>KLsLs{)h6CYz9Mr7CvV`}1KSP5R=`xt;eM#mWcLRnBl z*n8?rSoI*F8^|wmv^3`NW;skac z>p0m-jRWf)a1p(5s`LO?_#XHlYHs|35dhMtjqWASz05kiIzEuh33_yBtY%2cWJhtS za29&}L3{}hK8;&uqQS906PhzD$grPM@`*p=VlN*1GgkmFc_Y1&$Ct?izG^vPt8j^3 zA!!1u5O8hm&#*sm|L-q+O=}dkK5IZ5tas(-A&Clw$_7HvGE z#P}%*JI<6iKvo3cQ*ESD8pq#aG^spi-^{j1gbn4O@SlLs_jd}3AZdJ3e*&KhX@*5u zm`ibtx#K4x@6|b@fC?I-J@!&FZ}4q8IDqPts@tY4Z;J!KT;~i4`EK?VK-Md1=MDje zt7gt1g-JD%Z#-Y-)(mqmk(@L0WtDZAWKFn{H*y1l+10n@jd2f|Gw3`rC(V3e&QKHv zfUN9m=8aZoWG^I*25Fi(lc7s_762~?z@BUJU4jeZp4+0KA)4^XMOmX8{MJH9?$cnz ztKcb&W+&jO${#cU+bGOIZUS~R*Cd=V+iw*2Vjy_Bi3!Rl4Z`GQYi&s zPeOm+x+tf%WvMwAB}@gyrz>?=;Pb=fo7FOXbV{)}_q~`1$O?@B)%5vlGE?teB_A3s z3?_rhO)20xRL~ROW(50PkhmFf_IF7NzC$|T9h#XZ*ZASOt?unyJ?DMp>n91hO}~gEcIiaCAz~I zdrWdL=Lg=fL?OaxETa}6S>lI>{0Zi7B z1@02GP6bWUGG-a$QG{61Kk zK}bS=uL02PtO*-_R+pqoIR^{+nnZ|J@R5)?C3$y7RfbB1{in-9RfH>+BGLI+0!xo2 zw!j{faG`(gk~Ns_K^HM60r*5tiSwwAKhID7 zWKRC4&;2Zahm@Lyd$mW|8%QKYNro;Xow0_^l5`(7b6A%1J};Skg+`opj4BHfyVL9E zz^6Mr%Ne*RzPXeHAsNkrK+iEB6JQfg7|WEGBJBXs$ywm#5cadF`Z=}C-)l#`j5}x1!MZnK8z-Q+U_?!;Q{~ND&z~|p(oB>x5 zS+`iA?a#&t_@c5*S?%H)T2TBK4I^A^q&AoWLul$+4y{3Y2;e2mhOF5x2aeK+AVR^F z9$I@ff-vovtL(D@PW7mbRt=f30@>ibQ@8A$c-TAwl0YNwb{U@^fX~VhAU7}rsc}Z! zXkyM&LO73p+Wb5Ytd{SfAr$f1IbG6wu{KrVz|rbJH5xuc|aes9EH)il&gJ4JX%#u^9m>jGS7g9>5uz z2e@9qlCa34ra8cZ27L++a!(fS&qDCkS5O!BPY1?{YDOvGIc{!*bIYMGXnrZ{OXUl8 z(FS~rM&Jcq11$txpAPuU0DNWuKCw4hqnGNRAy}AAQfk9E66`G`B9cJY&#QnY=E3>5 zP=e4!kF7&VU5~>_MZs0C&FHiOCOIerc321?swGM9;51;_6Igh4yA%j?iUfP^xzE_# zh=6+pIHj3Ni$k@*R*fY93KMaX29(t#aTek@o)#Qv3-gtFUBHhI^L2o(r-iadivmn- z%EeF68G4QwI%4a&^ff^(!4Pw(#QH|U?UpYhIHQ+F>6C}ELB>zVF@R0SaGr6D0EBoM zKOILmuU##PWE{I_M2$6}7)JsFU5h$~cE(VFt1_T&dgoL!yC<+fT2f#Q7A=d?^Ai}{ zI`FCcjDrM=Un(#meW_JgSZmn2tRTm*K{i`iHIUn2t2D-j~f(%2Oi~WKQ!vK*E*l2DAN&v`5 zNMh8pr&75(l`b!hEDRACV&xXVWFJNd2_4QR zZ6Kp%rpuGFX&xz$AkM3vO=>m*hY{z}h;m~arQ&`qb>eIa@eC;tGiTF?y|vpVdk;lW z&!!piB@Vw92G~|q;0ml|iM?MGR6V{LFQdy87u=?gG*M?=bnw0gG zq%1Gc^aaKO+c@VhXEMV3J=R5^Go#M|rDpPIkE9oBr! zj1%V}L3E3=wq2x&3QU@8o?w!4T6z&6hd{%VpHN#k!we)2cmn9E+PQjG=suH3L}h{V zVxHF+57A4K)0`LVHNDQ{tRMi=eFmjAb7tsyVV|58mOiX00KqhSjP5nLi!A#LEHzCY zv1W$^(o-KJ7@d3XV%QT(9*>BjcV{S6kKW- zcJMErOFO%V)I_2`*8(E4;@$y9=bCk$5-o^!0fFiA1Urub;QFT<@y zPRx*uoUC_nT1s4z44^q)hXA@+%FtXfBd3%NpPi>EQAY}ZBu?GE2>7vJm`!W=GMhBl zOY#23pF#7|g|?apa7uj{HCpKqo5RS%r|ESl$!7mnopkkS>QuDU)9=uOd zZgmD`&AbW0SW2p2iY?%qMoEH2U@7aI@l5B8`lhOth`z&rnlo(nG?@8;`SPPQvVi5; z3R;UgXL!yPx0x?GXFQ<<{^4ZKFmI9&0B|YHobjt)s%v06XBZd9C$g)}#V;xsEeYgUvT`Ge6PRw+9!|^IeeDmAGu8CB1>^{Pfw78^Ub( zDLoclM-vXb4jIQ0{;0lc-vOu3gIoqIQP`gr0ANiVu<60vz2^Kl22`VQ9c=zL!+Ci} z^VFi1MkcVpJ{R!m48S<1`pQl9RTLj`agoF|4LMh6v_?*X`=%8VX7+FV?=JVOoz!sv zjCQ%MgCWfjhm`{ua0l#pXY%H(0ei3^@6g?C5X*Rx4Dp1yPxYS?GbA1{z;gAUG^rC% z02KeJ$3EIK=Rf`vAJK<7*hk|N6b;fR%J;0HA#1gEaVq7*Jq!>VZQ3)(f?PRC9CI)8 zb3xf4mf2+ehl1m^@1!!!LluUXlHzGBqlud7+V8*`)&!EqF}n6^={>XV>*)iusjIB{ zk%?t8NOyd;>(_u!*y~Q%ej3y2nS>NG^V$kKo#%%>r}l1eVfxyq%B)u}@S2B(3}8ot z&MX?IS#;G4wH{;^bU^{P+j_A4rwjkZ7EFpOK{M022NF)M$o)m1JYFJxM5i{4!~otQ z&1PAb0IZ`X)s?Ep3?b&2ME~m@HGF&IY={DkFRw%f^#i`ROJhv-Jd3jWDN*;)EG~+_ z7E?($_JR?W3feoPhrC*iOr5eXizHK8%08N0KLak-B$LRQK=MS#jrd#jYAxt@8bjk<<0cae>po-_3ULKh5NcJDomYpPiAPtGK1PBG zwr3vyy$JBx)=lzf*B#^YXAk7>^sjfo=ig;CliWxWaAY1mu{qg%GAmn8IZ8dLcA2TwlL$wd;xK8J*lh4@@@~q{Jh4Tzyi!KweFe?HRm#E@lyLFcU;@J4 z&pk41Dv*Ya^p)?V_h*`Y00w9&zbQMs&d6)(d0*50_LMHFv>m1-L$#Y|z5%$ZI#H@G zHs{dJ%yf{?eP_Vn8qj$)@Kox7|ACDKdx-#H&QZZA`#}Ha_(F`e`%ap81Z8Ojt*{k@ z^eN*Qci+`gri}c!dr7(bl`*S_F5sRSsuxtlP1&s)7@;wkQfGP$c7!-c!LRjB_`im( z=1+_$W2l3vB@AdNadVVN=jH0MGQ|M#oQ0OJGZ<7jnrsre6b$ zT%j2@T3`$prR_tHO@Ys^b)O-?OAw^9$9ULlMrfL)_fF;b#XkEC<4;-rbe|bgGBdM| zb{>0-*=Kmxb7r3bSnp<~e*OYIyzkj(-omJ9%OL~}1wV@bKB_h8oLh(KLdg>BmTj1d zkFW%pXbtj=vBrnon}X9r_DpRqroDF5`T&#TtTZ4nLH?jYaF_j;X&92H>C3K}GXps* zdyyZ3^+6+?*d`4mQk2;noj;sDfKM09l7Z?X5`<_Ykpc5b8@4E`L>s861F5DBzBB=2 zWnHu>pDJD)O5u1<@(AZ&YN;v-my61P&Y~ z`MfA7py!gFPb-^I3G*zudviwF+5hlrVYBJkq?%>pv&q2+TXezmjK^JNNebw5;B4yi z%s-q>d@ee_JBKZKj+XGtcO9w`XdppJtFHZd_NI^%ZOF;m*L9!$c1LnQ?!mI4F$eE? z!Dr@R6htQbVr=aZ0V<8hL0(;-rhZ>(kyDx>bKn2}@!oe*yB(Ax zb0$F$Io)T~0Z2-g+_FZJCAp5Yc>oePRkbRw+ClThOocktIOE2e@i;^JA^POY;|%Y{ zbA`-oPcZ>^|IvPr6`&+iNO8~T8f;$l8 zVe^zgfA~rq6&TA(Xy(sV@fn_HGmiEe(ZHjuWn!!~BIEuRtZ9Ne)cRo}!CFS^8r+W@ zZwBj`^`G!_48eNTW<7%sz!UkwyxIq(rGv`VxDU)>9k6`!x|4Ybo+<%=DiHsiCDC4J zBO=Lk=TLV9nLL0LQ$WVPwA!hUK9(%e5L(g@pDDxo2+O3WtSvR}ztDjv?hS%*KjhrL zIPl#Mt&qrv)A+>Hy*!JV`zq${S`Um(-O*fh8lPw*pS-}-{dr*O{sQgXC$QqLH+8>E zA;f`@+DkY>o52#Iinf>9&5YU(eSMn4&6#nV(fmGXH*Q$tF|JC3=HlMTH(f)KwON3Y>>+(+??8G;G^QtV8B0L8#dGv63hw$1ZEOgE-6P0b_vAT# zLJ#c6?zG+Q&e111>_GGX8*IP-4HqG68QNd%sgrXo8J@21**~8g+mDY=Z0k_%ZqZ3P zN5IvuXWcsC4ttWB3YxCtJ;?y;t8FZ0o~hkvPf}}KtQAmj)yQ>mg<}M!OiUv)Zr)m2 zn|19)Ye<>l>QJtPFbpt8^0i52YUpxn8`?9zO#3I8xg*V^b^G~w&3<`)MwE24-&(je zz;#RnNiojIpYZJBJast3{D9&Edl=!HB>|{Zg!&_mASta7m8R+x_Yc&m)=#yK$GAcI z)sCMqzBVHEG`?nAi5qwfL1Pt*gKz2g+RGFmpVjxHuHQ3`jmK{7W%SG;#59{DSC8uR zeMI-O^?$ODVK1{zG<(d2pPit&&R%At#@PF3T_~f3S)-+#TbbL=g6s|l)oL@VFx2sJ zP*9aiZ8~Vz8giH?IAGAaNo8$7KsL~5Jx8NeK_oZ!a1F+R@h1M}wXSRPMcs8+@!E@V zbi@Hqn6+HAe8ciH%XVjtK&AwaybbPCjNuct0r{is%kL;sKisrO&vxve5B6ENK(?}0 zQ#({DSKH%j)BLl3$-2WS*s!N@QP|lp#hLuI1`wApf{YZVTCZvTmp^jJ)qsqz5}z5| zFXWfLvcl5WXwA=|5R!DH=Mv<#tfthb+}@V7weAU%mhKKIS0|vwooR%+4fet#2@SPslcwf%}l22O>WqK3ksn{IgoppOda9KK~rt-+!vpJaUF{VcN<#yuWsP z%E|JL5*kBejZX#zEdl@V#e^YkEF4%`h>h;yVH3@hkr5~uGLuc>Z2lrWtfvU3bMB;H zJGO&oI4pmN=@oKn3Qo5QFIFbMAO%psMc;{rjDsaVt}v50g?2GoVs%lEy;VF zEIG!A1*e0|)S0ENGB^{l%b;DM}=dCP;zk(B(2_{RK>@Fe`*qk_oL& zDiah?1cAz@Il$dDWl}apv>T$S+ix5oXbf?R`Dp#v9&H@k7CuOE?!`oN6B6#GO;syy zl38GqDMJj{xioWdaE_0!<|uCGnVZ(kd`IOnIdQr?6+O3d41bMijuK}FjRcwqj=QJu zZ8*cJ@+_Fehd6eJ9Es3ojtOw{0Ov;co}qQheT47J5exTFERWd@fqv$vAx*1%x57pi zg3t+Oe3X&mu`qkJ4mG>U{FjqOq=m`d{=;?Ke;}>1Lpy3>f`e)8aj3{5q(Ve+PVB#C zQ@Mn;5(=1E%&W8bPV7L)9$_|q{3~YT6k;FE7Q7#vqoL?|RLrcZq|K5L5f`73_EWa=(Q=s_$YIc8?WQ{yBYzko;*?TukuCSpOGQ81QgdmMn!W_Hin2ohcWb;RdRzp3f~c^V@X{~U^OXfRu9LpZ;YXPE$gk?j_B!MzS~MFkaJ$h#yNq^% zSy~;fEQW`RE0kqZi;vJqZjLC&Ra-%uZdHQjE_{+ZU`e7~CQ|F-f>`RKRocg#xr=%2 z2|*Bc*LQ9A1sZ3xN!)r`olSECe^&jGh|get$`MT9+MOv|yG0Nv4o3=%`?xaXQ0^V) zCY~H(69KSJP9VJ>y=dCqXBeWZS`Te9(XwyKm?%|z~$m0VdkjKzgpZyALBxGi# zg{c}yhG}#Tf@47d8Na=vO~a}XL}r+f!#jyp$-aOd&ys36zx-@=4!sEq8T;2 zzAYK$~J*)A~Z|3~3~5eW7(m_>F4}HhU1QZ4OskN07cmGT)&iK0g>ZbJxQ& zw8V!gQV*nv*>ZVtjwtJ-g$thw+JY78xI&;xkrJ+sNlyo@@-r%eY($?w$Mp34aGOo^ zuI&@>a6^7XkI=ZrG<^i=A>t!ZQXeS0^~_D?XZbAD^MZEsTI8?%GFZ#7i^M-FX>I?C z+;;(hY{#vX0voScq|VfhfmviE?e(s`!Kx@n1$mXbMw(ci0CZsL0eK zh))+XSq>in06+jqL_t(Mg$-#R>EbS?2cg)*)G>_qTM|Q1i6Z%KQh!^II0$@-KUsnW z;WfdH`%SkYo@8HrQF&mlA~28(ZiK5D1vr{Ri?0O1l*8qOf*Tor7|(X@kAoXweIQ@$ zw9VfJH<~!; zC#^$`GtUST#yF!kvu|kFDxILcM56f7WF0ZJ6ttN#hyPlqR%tUDU$j1)w3)ExTH1{K z3l1PYTPORp8;Bl!GVfuIx`{7;wHpQKah5}qqJ>#7g&D&mxVXqq5fG}%*o`JMQY4)M zr|+_`q8gcvL_SE+LWb~cp9eCcK4Muk#34UpXnm?if_BG1gHpwyzDckvd{Z`#?OvyB zkGoTLFeu|26rZ;oIN!!K!y*Tklh3u#aNfj4g$ta9-^Kk?jU6*ilIg0=p_Qq1!WuG# zkHv&rE8v0yzwrmpw{6R{8~mgblx(tAU}|(L_DQjPePvi&L6dH<0Kwhe-3jglhX4sq zaCdhP0fM``yAAHa-QC^Y2AjF$vuE$!-TQOqJf}~0S=U=tT^%MZ)8b@jxmRG!W>1lXj3nO!&~Qct33JkL#c!Q5QJ#Gsmp6YR+X?}g;L6QE+V^a*_ZGQpskBQs)Vx=ES5A5a;cAG?_6#P>>y`C~Jb?2>?w@R&7pXusP zfn!{+9D4P#h0^IV)26JagLPsr1#lSRxOicx;tclCc%Ofs#i>kwg=M@88-R!1hBg&A z6xo8)tLN9Cw60%VFVqVKXXjiwBI(f8Gp59;HkLkHPe5^-)`WT`A zd%+-fiY(3K(0TF6g!e#hFvS+|oTeW{r5YMWJ^qC+h$Kfo*BTF7jTf6T4iQ0K3N9uo z;-k3w2$N0WwMVJ8{zE~7b7!z#vhLVTsOJXDvO)G1i8;andyIR=++@AXP@Trt1zvH# zaq-dgiJe`JWHD{F z{y^!pVpJCSk7z?UN9Fpv-aW$TdG*4WmP-hp(frAW@CAeM!lBz)uGd zV9RcKYV?Bfa0&J6=w`MWa3&)h>R4n*s}z@c%pDj4uWshv=Ied~je8+0d5;|UJo+Hr zUb=^Q@e$k=?DNL6u+2!$76ACS>I6i z84yUm(=_BDyOB;#OnDl}TcK_hM4yHr{`5?RO%Z>hZ!3z1)Sid%;*^; zEuOs5qi=%t!2gOkn@*e8wq_wME@N8F?qapw3s|S%fms(8UET$9VWiqX`%Lr9TIFc^ zFITTb+}bUvV(xx^$JJv?D|bCB95h_WrkF^cU_z2c8)=Lo@_V018DmIBG;T3=Hp?vf z5BMR{<}E`uGDNeIuQ&YJjx1c$&;e7p2$U9~J9N@5^umtu*HCZT3bhC204g+7D=o!wnRz^M4Dzrut5ONFT#4tfon8*ib(9l^6881( z0rgEDDTkK^oHfZ^#-4e!$#Vx~wF0WokBbk^!kd3s=tKb%$8v|>N z3fp)bB$6Awcf4W?*EG1y@SR**>XEp`+|025c=y(=v~RMRFGPq&euH7i-oMsIrbUd- zB?lfd#7{C68XnRKhtSD(-Cf&<3RJh<2wpOIwapc6fE91@!4L)y0PEXu0o( z9h+^dkcgkt5{!`0=^d}dPWwaL!&#yObqI>n=LI;aR~5wpym36s{1cQkmLCKDNGwJX zH>}XJ+|$ukX$+h{SUt9>k{kwzSaKDZWJz;i@=`R+G>i%yren5ixJTFcHI15)xHi#_ zOO?P6z?Wwd13=ac>zCmn0! zR1xXyX7f|2Eb}Wamv@=07FTmCuT**xR1Wv9ED`&mv}r1wIPI zD(|~va-W`XXHwy-9VgQ^)e)^U^ZXVW z7{F2`9`BMzR|yfXMWlXUBUh0+PG;leE)-gR<~_(bbJsmV<}DGal+hLJyh-r~ z7F4Pw9wtI_3nz<$m z21^cduh#MUa935~!OF3vgvX4QoibDG00?(~IKBhAfm+CSi^EwNjEm}q1<9^9pT@5U4@OjhVVM;cfHM$ zxMngiDN^e1d1a!-XN(qv+E5$f_)d@YqsZ7ft%41M1*hDV;(`UHeI-s|nrH%QoX&ss zd}CIfGqwMItfqng#>@4puciRhmE0qLox5zeGUE#I29fr^qxGPFXv$|88Z|2dwQECpv}CBCI| z5(1+0k>;gNcz%!u1PEK|_v{GFgR>GGXvSN}z9qy@Job`#c8hSO#|t|vmh`4zcqiYiUbqo7 zhcD+#y%Tw}O$1gAs?02}<{e6NOw3xn`IYc*HBJMtGiLI13y2+(=% z0MJBx`MEiUmtub4Po2xYAcqd}#N_o`2LGrW^zTMK`D&WzgCBQNBe(nvOfqDU7aS=r z@P3ONJDEJLkWuWZi3^C~PWF>S}2wb@Bl*vWPbrJbvE`eM4DlAZu9vP;gcLi2F z3Q$Cf&8}kyRzusdfUy_ULQd7DC%l~LgtQKjR^()h@hG{uUK29-7~uE7BOT|uJ^Kci zCY%x*udaLYWJx0G_q7XJZXi?0dbg?5#wPL}M#~&V@augqB4zrU0}sv~~2!=;}OO>r>Yy z{*bg|l@Z`R(APN}AP@EM{XU`Kl4c5SZi(_79Llj;Y@_hp@r<4Z5FC+86scncO&#u= zeM2`p%Z!M`zg^SmS>n;R{$}iAqdyP!+HL)s6)4xi8^db!HKX0U$`L~N=#l=VzXv(n z*@d=ZaC1Qc829xnH}ad-IpppUW?=T{sX3>N*8{Rvio}Q_hbv7f&NTm}@3iz0?+oRf zfvyk6=G>||D$&`hFG~RBd2@CS6I@uy`7a#0kK zd0esuHeAFF3SCl9Z|~fmH$_ah*XEPe+eGj=Nb+fAUmL{gw;SW zm;XvIqs2DWUEpmUgN3Af8~93JX0282Lbqx+Fg^VIq4T6oNMVU_qpWx7mw7fvR*KpRnw8Xby(D@ypB(xqs7?* z))7@wJf1fTkivjjA-zNumk z^o=Hz`7kLMM1i9%+L*v#z+6Y*2#Lp<%*hL9?ijkUfAMPETT%qU+_RmIjCar5Ao|3! zpry9!vvt9fV)Um1YiEy%F1w87T!yo1dJ#hT{J=6@xlNgwrbXR16?53RrZLv59CuQ_ z=Hqm|ZVTPd0pdh2L=G2zkN$e-oo4HiCY{fKqnHu9^tM_1#av|aMP@BIXfdDn@a3h^ zTJsm%J99EusxZ>XYuub`7m@VDM!;;sLQj66^siJji5L!WnDji~ucBO<9|EWBi_dTK za-s5Hx2ME?#C@_s(RmYIIVprg z)l1C13<}epkdt4wOq-J55t+}4tj)+hted~W@)|Ba(Dr*(HTm_k%2Zl|=}+4C_FuB1 zTa_EFCgn|bt2Zu?#)=L(5e!YofVCD;dEuAhOd&8HLW7w`a{8m&{rT;v$ck1nXJW;(NQ2I7q$8i^}E@7W?QJ9V{)29G>ZI)*M zR;-fV5~boE%}frv=?Li(H{c`Q;CcJqtdpWqm0q?h^0QN(BR(P~x@3TWnzEPY%v3r3 zSxG{&Qlo5P*egAd-yccs>>m4)g$i%l`r+L=h?AurH@gzdNo8NqtGBf6lCvEQ_w-e8 z{!AC16Z^mjCl3^4V4MZZ#Ds`cQ<yA=IU1_qFg`Nt3f^WpjT(@3hPn0g; zcPDT9-QK;>LNlekyvi1nhS3Cyuz8_G(W))87?$}qtL*1aVlHf~0iO+)o3+ zOOBpmnv~Wi?~BOAy;OmHOOGJ4&TDOTP>ujB&yNJRSM~k9-S@)*(W6)iGPp1zZ@G?i zw_SAUS*Uk|IprJ+?R*nojZRvth`&Sh_^C(7^C8~cyY7ww(RN1;_^Yaz2Ls>=Y!PQ> zEvjppiACLi-#PEm;P*y}kfGRe!s~w>ZcboZiD2u$96nUC&xXdo6) z?49q~^SLtEa8S4@F^lTI?;q`g&IWD1ev_45#qtoCDA6eReDO%1T(w308iWFSP7PcI z(s{Gs;ym5pqMXnH;Veo_?|tt5e#qPryaC6#WbR~_2l1wDUKcmLPv6f`VTfwm@RY?( z5DHaXeRiw=T|L*Wcd{mc?`(})pv~aXcG#IA`}Ma!B&z80p9C7 z17!WC4dfsfnZX7V(xNnNFD`!_YtPmETKlVSM%syLo?n&N9q4^)(~+S(86$QfQO_vv zO~%;I%pwUz|1{&;1%lc~If+i7JxgDa|0*O1CMSAye0|y6J5AJUKKw|PvQ>JgGkqt& z?;7WK;T$F$16H4Dr5(iO#|n>*C|*|=CM>AT-o4WmBCXrUQT-_ohw%VyYX-TaN?%59 zOQtfNw&)Y5TA0fqLg`MUcURTk3hUR|9K9K3*VD9??jS^M1^ISyQ!Nk-t9XF^Xbrls zch>VwC#eSD6c$%s@3Ln$c}#SjF!ap+u;qx~d@Bb8I@06doWi}yf~iG*7+=d)4K691 z#5Hy46OU%Vv9Nu!8Xs zLgm5+@q2JdB~u}wcQMwVdS=l17c>DR+KbqMf;-2$(HlXl6!gesh= z_wgSQJ`N9yMxEXYXg|7jQtw7wJQugB)Lp)IKVD0ID)@wl@J)cl#T>zdIv5#lvLICH z)dGdA!_)lpqogl;bLbW3K2_?N486jX&>Dk!XSnB%Qx%a&qh%y1y6_pa4%vxBYY16d zVOZu!qRqtsQ#$yB+Llc1`d+Tj3AwH~-X?rU#RxpD)CdORta@Gl;btw@8?@<0U>um> z!(oVggqV1*u~83GBEoH9Gpsvs)L?koy}r$qbzEluUR);s*>(zzKPJnj0O7h6Dp{RH z$-DFS5YF8OMz-Epljl5lXaPuKi0E!uNz#ag62Pwl3yd!W_I)xp!8H&#Ox&?e!=d5& zpf5WZkK|_LLhvONJ5Hx_zU$@~M6Tup&G@m{F-rXeReLi1EjH z@MD`n!9yYPbt}s04El8WDGW&uDZsUc0_J(|76i$V%sDM~&Cfojoz4vW7L2(i4A&0ngU;);%9OYv2g(?#*M#YyK({&^3%&*D>zhq82B zQ_K51+_X=VLy<_^b*Pdh12ngs0lwIdYGhhI#3TTC&L75SbRJEoalizEpo=GXt!#ea zQ(v;t`COEP4Mnys*DK9q#%QXWT)*Ea#a4+&>eGn)cW9Uk7jmQNA-YrZFvjrLeYC50 z;%pE@5RJ${MvlRCw&Z=X|Jn9a=4(D$L9q^-xqIrEXBXHTEt{{R=RR!BX^Td~S8tz1 z19Z0k!b{;do5~2$a+5N@TaJ~TxL=x=c(th0*JVjRSEFHuLl!!--6%*+yGpUw|1rST zen96CReRGi0JCvfZKaj~HtWSMd#G8MxAjver0^W8QN|NU5@|Lf9K zq0v8uZHJ|b#U}I_51Ll=YnlS5^3`c1t>fe63%_^M+*=dK5^m}%mv2;ccdiVpV{7&+ z+=hbC_T@j=vSovc8_i%F3aM|L~8c9qEBIAOG z)-)z&#)fN64Y{JEwd5`rapxV)H!0J1w}8YChBu$9uG9uwuSY#Jo^R|zwSkQ{vkdxG ztqcRBS3m3+f}%!an&=)6Z}Hxv^+GQ9{rhw8ilvkI4C(PN#WD-@rx6m4|NT5$Upk&q zqG0pKxK*kKNwRVrTGVQ<&8A)OMQ`sLA1j>D+DBc;4#0KrL7)jL>Q6lka39RDXT9(s zSaJqb{x4zMLIduhPURYH@pD3yrdE&f(|t3o(2-vL#?~)r(ZVSDk%Jc?#F0cPtqX}m z9QJiFPaHB~{;|@xIiT_I*ZJl7>g;%$V8GoPpSeansctn&&HXUM^#=Q1*yQ8KLcb{z z$!?5nKTI~T$3H~-FGJf&px=eq*Z%b;n|#N~+Hp|ux8E`?XcTi_$JF~d9P{XpUGblr zkIi=`&@L%t$lwrQwac_E764%;o^(-h)hUnZ_E!i{&+DJ$@R!;`5G76V5{EV!#Vl9T z1dY(keXd~dGECGND{CoHKog-$@4JEAymWxn{~{)z)WPxkd!ceuqWl)vjqK`nW*^f1 z1y-ujZKY07teIqyf5F7j#M1@;A}s*?DfzufO5#KRFROrxcYkPzqGwHc6yi- z5VQ-Gt$MiWN7`n8NYqd__uWtTD=eLVpP6qs*wE=HY?7tX(x>MiUwJ1Ah7>8fl9s3E z@NWjU3jc3rht_mBwfo+EWwpP8CrGqUuV^60*P7Nl(gZh-$tYHN;WY=PT)=%+kK(2% zkeom;8OHXq^xw*#^K(< z<<0eeNA#_oe29fA&n8~k0M**qX7lm#+3d_>1b_X#OcI}!Z5k%5Q}r$a+coz}ARIXN zom;&Rd*HKw==%Hg15kd7@Zi@=NYMks!tVq+Ry4ZszQFBC8XNni&%b0q^h^DQIlM z=5GH_$UH|baGx}sW-RB<5*k*w9cGc0@YU>`Gc8{9#;XtK%ifLhch1+-=-+>}jebpt z?LS1&AthxBgo_n3N)=G4F-Aw4G7pu`A(Q*bmVUBmGsWvNW9fF*PE>cRHl$_lWvIN* z@}D>VAp`*^q5kB(QW$h{CYr_jG0qd{DNVbUt2;c4$o#W#Ex$ysVg-*d2vH#ci{~0` z>kVq=3Y**<90bq*XW=C15Kk7E5SBNqp=tr- z&T_!X6Ac6lXQ-H5Z>d@>Vq`MG2Fr*4Mky%~t?XPlE6xS0i7swB`jQ;&a@~T79eJ>3 z+i|eBvDNuNq}SCCk~apH_iuYlYrKX>NB$)W_l}U@|Gl(-?qC0AgCEgfMPDpERjMj@ zH|~37eP%=dnd*Pc&)>b~pDe=(`m0qbWv{QFcETnnt0%mp1pm4cfA_?HkNIDIdNcnO z9jDS#7S`-(^Es8$1S)iRnsZkIns%F0XTF8Y$F6Sl7xI7U?Ef|hBlI6&ee?i2wj3oB zv62Q4`kRIfe|C9??P@DgpppX#;#Dqq1WW734^SkEnq-q~@oW?)h6D9s<+$l=SSW{| z4>4THb^oqI{6TTS>bX|+q7%7UJLu-$i~3(a+s**FgGxcWA(`)R!4@bPym_ze2lOK6 zgzaD+@ew2-t=?^YCtZlan|J3Hz8>WcekOhvzW>rCDde&p@M5x9U#R%MP~u+%{IB2g zNdlBpIxpo+5MAzTV=!~Cq*@jfg)x!%@GdE|vl4y{P*X;|NQqqipSrl81DkeEBa^i|0hQO7$tz^gmu;iu~M2cR@nrN@3h3eO5rv zSHBm+Y6lDP1b)m$)D-DIMvTs|bm{lEMuP^5S~IzM~${q4a{r0|Zoc>*K#ZPDda z!0?l3aG9L3idFo}p+=D1SbeYm3WB7y#Cn;H6otX!gvT%!Aci#uIRvoX`IB4l)rtNh zvj58O3eNii&YbX*RGzcsfZByY2TCNWY*DTGk{ROpEJ|!Ho;WSKqu=w|=*8{je_v|5 zAY>NM+pFT0#t$XHe(pj6J09Ks9hu_rNO+6&_yMky(h6eO6?5TrTKav%&`!zOAr zYYh%DH56O2mo4(`Xbzz+1g+eJsfnY8+Za*yY$&mjpzD+f^(i@j_65fTS6n=j;8Cr4$N6cNRUg{S4b)c9j}sr8=XG?PTLgF`}0kt+}D5VU-_Ntd_W3i%n5| z76co{*NwdDJdBlM#ijEnl~fgtb4NZcr%P*>aoVJgw3`$iRO$?AP-WZ4g`h^|5)$n1 zk_+8e4s)GlX;ycd_|U92c5Wi?)mWJ+07QgW3Uy=xZ$j@PXfUwj`|{2Cq6n|?kVIWw zJr`_TVyf<2eK^afzV4^Tv4k1pN~)TsmTB5fkhf^2SM6CBrZqMk1HN-mgg=~M=MshR zb9vSJU;_3lDd484rGf820L@(wu1^(@VS;&eZpS5EORL_H+oB&h_31jG-;2wNM6iAV zq-u51Rny@)>3T}GDu}YG3A=MEho7*4lLO-YfAu?n1@o^u|x!h4`0UtK$%mZl9r5C^C6J5eKgCir&ZZz$1| z#)>j2XB@)Cy$8g6!h@$DtQ#wXO^wPo5Qmiff6j34KSUc1B@0bkVNRxEC6fWGwGlOS z7+0IhJziEnN%;91Uti=(nhz@PB}=V72=6wwJAJ)19;MML&X#Hn6cy82z!{`$NA`Vb zq=Fl0$PxZneLwEEE*$hT5Kz?#Qa|pr@&uo>^lYlO6pEjR zy5ll^tgPRR%_hMHW-P=@x4n&mf_N+O`87lho=%&tfDtd5h?V@-juTcjrK_0rDN2*zyjdK9%av(v ztwE2(ZrSgx)d2i-+S;+?L()ehwEhQvMv@}&ZWn%Roa}r?I!>Sl=GOwjRx%n>KQn%~ zLTH<5Z);|i%eD@GZ_NEtS64&$fceZ92YFEJw@$bzp+QXZ~LqD^Z#?>z0;E`L~)vw-0>OQ*knhyQ*FMpD_5!1@A zOj>BeSY#qbsEDzhRK0?y8FLj;jkZ3IefSN(xUL-Nhz6+zhF|ikJl3*(Bhkr)sx<*O zTxsKa%QKGsz8VZmU3v^lmJY^Y7XgyyqspPLB$swGQ9+IE4VPh)!W}n4ZHS@9ds_kG z38+;Ph7q|A$Rs<%1q9+oJjzi{IhgwwfB^%^{YQ3E+QH!c4~Cp^UHY%Oj+btH3sxKh zWF5?Jjgt^CL;!yM3~zXnXQAWPJEX`r=Na@;=r{mR*$Er^w`aB|7h;;DtgV^dfC8$` zv<~EBfcDzzLO<4*koXvGi-*nLoS2d1_Xti??Vg712zeoI zc+x@T*RfUdqKon&dG3Bw!EieXX$sB*$5eoV0VKX<-~|2_w^CgK!sVX@9Ihe2EXu9!!syre)2@^6c4@b)^}*BylXv? z-oN+#><~vcn-#*XQ)45K)nf8NMwk~FULATI97ekgr9`>S8#m}kUnublidfE~&@X9k z>&#uLwd~c|b>tb7+l|4=Loa>AjVelS*_EsmR{|lXSnn3=`f?8LdUm74RDn*}kfg9i zCX8bZr^9YVVmoqeS4Vq88sMuy04i?)=~K!#CcL+tcSt8p+YZ`RN3$=W*`=Z5OpL3_QCf0~m+>nU^QlxsJC^N<*CPV50O^{aKW=!URDC~tZlKf77}s)~t1~W%)Ej8!dTczqTE^fPLw-a?>YRR9A>;pr8KXA0 zs*}G4kStqdRPL~SN|n^sO{wD#r+Tr?<)jvPN$M65fJU1x`&^+(qsvLTnsSHX*V_l{ zoL0P0D%kLFth$zEyF{HPn=S>l%7=$nn=P~>nThXWi8?qmcNS5(q4_1r+PBnN@`Rbh zLW_B&?$(T;@o|@e|Mj?*$ohQQ)=ruZpF;C?rlubEo*i7BI%rD(WY2P;-i{XZW!P2A^*mEdM2v-srtWR43ObpRbx@x zCaGwisYqBbLyUJD+rUUkFk8lyQ-RKfGb^%5(5*pb|E$_b5YGGJ$!2%^DQaD~jut2JOk@@j2#1L9V12Iyb+^C$fP4#*-fG;-_JTprFm*xg!;{?b<=BZ1?v&Kzi&S3Gh3 zglF&htqHp^+a^>Av!-H}uU^q7C`Bnw_tg^_Z7A$EhAT||^_n@gMOBo0-`LZlrxUcA z=5znZrL9?xI-DUwK3#-mq);yCZpaVbWNCaPakFtVTIGgw1wc^LZHFlxHuj{q`n9XQ z6c%8L&2nI}!?!|xd&^qQ8lCLn zW$r&4cHOKR6e{3;J%yYt4Cu)8S>-ui8CdS-wiGw>WoCDTcESH$aE%=J(XTErB<^>m zcZ2!pVk|*8x130|3cIxl=VG|I5AxHiIcHQtl#Gx0T1N4% zWp9yROu>Ee=1lts+fzww5PFSIEp%-i8BP2{hGfRWETKU8Z^?{J&mH@*Cp%@j;`R5Y zT((WI5|2xsW~&RrtF=z@!jI9~jdD|w>-kZ)4K#Xrik=Fm7+WRL)ssI?mnKN{G6B6G zvw>)TqwSv>7lGbbXkd^y8ETXnV^(&(yTL}?K5x3{uBU~;N)k?SrIz##wec1u+L!YB zMJL}d{`D%PYZC$4oYKgTzLdXMj6hkRT$r#f(2u=GCtR~vmM5I}m)0B1+RPrD4+(p! z*$^oKR6mWE{>UH)kH{sZYVCBI%Y`S3V2^MohHLUggd@Ci&^Tbtob~fGhf%QcEXEXG zN5v}AUTLo4hY&1COdvN}+j(s+e2%h#S{4>t&AhJS{nXN%<>nBw9X5$FfAi|}-yoSD z_FwAOo!lxhBJ~v`IFl(*Kt5h+nI?boM^hB>KEPr6)@mBNlg7mOO_%bFL=B-)<+Uz! z^6c#Q_pWFD8@rU?s#ds*^57cX{EXaZzMfMO)OJt+(XNy;)OayYh`5)7e=0w}pM?-z z=o_k&vRT$A2tE-V5!BSx(Z!=SR_Mz$`oGxN`5pfJi?#~lLW+7#h&?->A{Y=ju(UH7 zmhjmVO|6MI8KX3k^$ibGYIphaU0rQ;0bEQW+GuOGeH`p^Qv4X?D(seL+K0{vn?32S zaMw>q%bkMWp8Aim*a0D}=C-caO!%Wpnij`a401_bjlrb=CN+B{6&Y=zUrKu$@E4+r zjB|qZyQaUTkLk)?3aEZx9>h^bv6v0i-$I>!My}9aZGy506z+C`t!wi#cE4hUtkfpD zxz(L;dS5`UR+eAY|LX~Ejf(vR&1ya%UyFjoV}b6-d@x@Xdba&@!x%x3w*BI1IKn*P zqnA(ytU(LtNko-i#n~^xCgG?s+Oai<*yRs`$~E9~t?7jkGP!qb zsg#^UjtFal;N&J?tx`M^z}{~$WiRR_tpdBJWZAy0FvqU zDxy+vRFe3cNNq$vk|t%&p+JG-Dl_?uHZa?O=W=GC0&bm`u$nam}S<40q z>nbovFT8gSpT6f-CpP#@XZF<->+uF(cyT`WMETOBTcC5!?>p>;&_K3u$19>QS+@m3 zYn$H)N0-XxCpw>ov2gFJsHQ(JL3$%NT4ACf=G%gHUd{oJ=Ds8J@@pFBm%VC@M&p*k zRW*^3aH~ch-FF0-uwGK+zycw+%;zw}E8Gyjs|uinvKX{iQr2Nlk(0wJ=Z;s}QK7Va zzeRK4`FSEMGR?~s&}8&@X|q(_Meg?bETb2zorprhfrfQ;Z2Zz1IdxHx;NS%<{V)sf zFJv`(dv0tvg>Cf7>bEmP7S7cSe=kp-B)jGH4xnb_D-oZ{ir)y!>~uFs7hZ2V&n8~t zHtIu`T#}5!ec`h-P+v`3NlDATD5LA>EMZ&01*<;AjaT>>g}ZTXfZE1&!cMYY*Gn#x z30gzSk#g5Zf;_MQr=t?gDhNNIEfk+~*jY2i0;*7Syy@L@>zd`u2}SkP`DE36sGDKV zRVMLTJ8`}5Zw@IrdN?>up?4kqgF$7!=Ld#-n{rNdaSnbc&%^yZ90RJC>-{!yvRC1Y zug-5WN~#Mq9>m3;LGD+J*XhwNs(9~Ju>(Bn(XX<*v!~Ug>^$~BHoD30XLsw1Nr&@% z-?hr(;R8n|B>%(%n0<3CQBJb;;K9SV-n__dMc0loY>)Rjp!MY8px=iB`pFw6dC3;1 za+C+4)4@c?lZKT_ud0_J;C#BRC#bNwaqMNZv4Z`*%JdW?O^-QSQ07#D4!HTBw8!6| z7ZCTan_-mmDpSyNgNbp6R&zl^g^HRfF#;xlO=Fy%y#~9XHIQIbjT~xJ zea(KV-nTdsMRtrl-JIM|ZJ8R=16G;SaiP7s<4cOQmIKVr?WsRdC+!zwAW(;4<^EB^ zJ1W$Z5x##mN#c}Ns&7{yt}r4gH&%ZOUiXk)8R9WUHZ$^tV;csIqfvwn`% zCwSq+t~IVFpbpK<)JI$1$7#TseCY6I!dF^4xEKJ1k(y?^DZ?g6zkCz4j;)?-%}3IB z{B2Uk>a;Uc=+%zjgR{#|k*;XL_;)gpL^r&S6y{sme=H2u`SP^}q6JBbv!|Iznbn@=pv9~EQ-illB+?kGz0{A*|k9r9PjDh$kS znC71v{#qhCEl34c`AnzNid5&eC|xB`R<5uD*^|0Dp8hk+9ggn{Z4k z7ATMwfzl}=Mzxfa<6qs-U(a_e7vo~lh77USc?T2!;HlJePnwIlb9pr@<3dBAWv+*ZR#QGcC*At``S!~p~ARlW; zi&nw@qEgztKg$OF$l`p5IvIm92Vnyk<~Tw68TzOT`KP5IA%kG~2(Ww2nzh5b%(?qz z0DYKe7rhGl4;0PYM<=$l&P=|Uyz$$ZMjbej!!Nk7dCh5IRP*ZgUZ{~=E+E>z-6*=>iq#|(cQMalcVP}*bcDXaNN(jVEbcKw_S{T>E%!G z;}q3D5_)SLV#b1^%Ejjk2dc35%^ONax%YRC%B0qx{5*!uY&nl9#a`y{DZ~YhOSQ+b zMNL5u8ibWir;KVwB zrTdL4mIFa(v-Q%e^M#}5l#2j$$aEyQJf5>{I~<37;|$x`FP_FHl_v_mfrD1%w7A9Q z;vRve7CwogQRag;$K`Cv4vo{~`V@|eUIk26vWAup$MTLY@Fjl#ta12vvUHdHJ(U(6 z{+OMN&v0WPF!+mQ@FR!avd%`C#9=0#5x3@8Ye~0^296{w_EYJ3Hug0{>YM@ExG73r zz9xU|^LFQaG%{;>h0z929lo*$J}N8AwXC}A^E(KFOP8Pja{ODe?)xatO^PXXX6lm; z8arp2q@y))UDBvYSZKxbGq?oji&S7o_~1GhC4Pl4*SQQy*O^ zXCUh8l~?moE>@nFYcuPH1%ybJ*kbDlQ1385=7Pn3n8|);7#q`eFb!xOQ4XPigUAxL z+%|Gu)RH<~5ZVCD))x_R!ez`Ou4>Yp=`hL19g@YEabSzG!vIdN7+N$!R~^Z=2-H~0^O`G)4V5Y z>s(ttlTP8se0C|oVV+4z%G|p`YOT|%BUlVqtc!ITdJ+$BLLp;rI^SktIop_=j5$<0 zH9M*|dA7!@bD3D^)UH3>)NMN5tk#6j&f2#h{Y{8|uk>hh`Q)_f^FVlhVZ(nmaF&D9 zG;1(oNCh`JZ^i9nGcK8(t%yvj0IP=hxzY{T7nS|mtC$6vb^YviHtevOHTBp;z;-Gr zSC!!)f9K>uzVO}w#ewzf(ANdr=@W|;w&!2cNinCVF(JyF8n^XH5^Y=ou2#%MP3V1Q zY+N-*m#Enxy#m;9_1JXV!VNrTSO*Xmz08Mjn1!^;>QxAp0z+M7u_rH?e8yPN6wM?g z=21)9LH@HEr67*EZIoSoR$_MSBsp#_VH;Ja*$5@gQA+2dxd!=!pzt>+{@tz8 z34?XKy+^v@*k|$EIy4uaf%{9Zyl>2*g z@*oRLTD!fZbZZQ{+UEo@gsM5hGo6Go+mc+4C~4yw3akBrwXM~!Uru3Cj*erv~~MbD-?rN|-4P&<&qY+{qt z`kf${W3V#%>L+5s!5jID19OmYRbvqg z;q>V(x6#cyWEIvGFrLvJ;BOj89WGtyfHjhger5MBll#wgD(#WB4m(Q1&-$eXt zBIS0sFa#vrA~9|+NJGgBs7!&2EcU`Y*K{gPHHHQNS))Re;#A%zq z`ui0C1p69ean{(4%!Zc#?gbEMj+)2QLk(l2WISaE^#UwCe;urD1aBJcV7MAX%{ckB zyZiYN$%dnxlLbWtV5ewm{=s}@pc!3UDlt6x@w+Kwj#!|s1N=OU41$fM;VwJnx%BQa zHTQwrzIzVwyW9NDn%)759Nz#gm`?qu+#`FOx+%~3o-E116Dyw+fY*cz9l)tTPS$ud zR1<^^zZZ@%w)SG3Qg}`V57&}I`?k>85an0g0O%oVZdIH5y|Y840G10d~_9AOcPyUIAQDY2GFXD(x0QJBH6V)=xzvAIVVa zwcCEZ)UEJH5Oh{W{oa$O-yhF@lG~O!5`mn6iRm1 z>J;?4OlGZyI4P|>e=b}PIhwz^pb)P^2xAOd^7MH7O0%ax(QuA@m5CBRdlEdEt( zuqZpeiG=yl?`W7zdZUK3W2*T#^%QDR<-Q|u;^$VFi1pocYqB#Le;elrb7VaQQakET z7w6R8EE9%=Lz{E8d0U?lZxp;J?cbDrW_7(i-5ZU9&-X3s;Nw(5;4xjF$@0V>Cs*Rk zrnR;9^;d%)+qW9?CQzgKa!=}hjJzH!A^*ec28NgKV`-)EMvc)xHAX>{RfL91&&Xm| z!zZJmyBjX)64alDLkH+K2#eWM2cX##ZQ-30qsfa)=kuP4sRb*UQySBhG_~#en~RZZS>JUaem?PIjoWx=`YS&;qJPZ3|r722X* zuxii;?ugI2_@pLGhxkiiB*5+VRvyRcDtinKxZ1(3-gru=QyR%G$!D|SZaf6Xh=4a8 z<^z}rQH-x&pXp(R9gWGF`JgB+^hhw73JV?&`j{oY$r!T-T4T?HnlBPJi1_ zQ5{Vxt(cO6L*^atVRjmTEG{!S5Ra`oA!u!LwB!=?csXx%xvFKb$Rb`^2p3<~5(Z>e z8l5Qg6$FNhjyU?w?t5$P7SWEi(sM{^XECiZRS?*|-@L{^2$eU?bUefSkY%$x;IpQ= z)%%G0f5`f(pt!=V*%_SR?ruSXO9<}ngy8P(I>6vA!F_N7A-F?ux4{Cz9THpu41PJc z{<`&_x>fsmKkVAo-}<_Hbq5c)w&{euIB6g?+XerZtO4WskCqIHc|le)h_jKyakFTQ z_g2DJs3ZA4J+*?NJk9XkgF&gpDI>(!?V#?2)DoW+S4JpmCR&BHU^6o7J>=A7dHYjt zHIdrP!|&Fq-4-Y6xhjWxEz_}6!gl8#F80kRCQ%wMf{&;%nO}ZL{h zYVuZ=-d3~S4{xHy^Bv!lsp>XzV-nRQBiTZEeuLopeNWch>xfasWfM@M4RbNqQ8;3? z!qsYG9`^4sH+4DvnBNEt5~pzcMBt7PJ}_m5oB_*3OR{?3A8~~q%`O54e{G4czBBpo zg=#bO#{ku=rul%+#R8?%+vMeq#U~HMv(J zJ>_jZm_RWZV#4PYRY#IO#<{{%#G>zsy|>A+dgGGm=#8|KPOIQfD{5C8fV*3@@t!Q0z%%A|c41oF2R6TPn+JbWLU9pJoiubZGI;X66CobRCbxXOnq zSf!z~tLBcv%qM9dh1?=Ve@QXSeBUEHgx-UR;}+@p$PJ(Vm)ocL%iBH{?}}&?1>Vl> z5%KNeKNXBm=$fSMoXrVi{|p$*ZRsZVI+JuhWBuK&F%>T_vtSIR!LOW2x~L>4;Oep! zI8$Cdr7Az2-kgn0Dllm9GDXFtChc6kg>7`K%N<_>-R*gFk~j?DnI}0`1U6MxTZ41! zU4pXe@ulq=JRB8JI#UxqjLuagvIV+RrTWZO(ALh`HG0kc{H434S#zPA zMb0V0jdR&wvFBCIVgByU=k6pkq2{^4`F7`H*zgOS@S_(i`&!-KWj%w)e})Cq%gU%f z_#b4@X_=9->71b3(P02PajkZDxH=jg%XwLD&8@yZ64+k~eN ziTfFsIQj}J0;8Mdc>KsTUVeW8*uwe2hP=1bqK~$Mce!%5=~TZDu2oijTY%Y3aV;C| z6wQJF>1kPP$h||GoN=S4-*SeI_32}6#mTtnH%bXMA?e&k#Atz$QA=%ikC{OCW9*>{&RUg2neKq%!r^7uNXxyLkJBU7onru= zy90Lf2l@GY^>M|x@BBaE%2^$fz2Z-?A=jp#GDlun_G2a9AFI+j%X=aGN{{47@TfRN z)uWW`>~!m=88KQxXU8oN-p0mHxI}^wwKHJ!C;3It{76;uVGB#6IqG@y8a>+gzo;4+ z!#K+fJ9taY4bS}Ld;C!R0xj1)qSYS2M{d{46%=HD+Ewja%-?|Kjek#d;^|-n|z~4x& zdu{&T!cfRv{F~o)lcx}}xZ7k$nTuHxkrLH4I@QQ!)O$xyiZ)@3S#8=OoHQ(nkT6Y* zE1PTG8`?7!*&w1QZoIAbuCc>YGnbx!1)&kU(lc>*c^h+YYZ%o%-kKsDkL-e<)up=C zgtkjm_;RfLgHV(^`k=G(VyqVIVz6SJbfsV-i%8Og)-C!|lgPZ?qy-lNnoPt>4Z(B04k2dL8rFNpX6E9&5cGE1lnyMmdGIUY*o6Ua|0WBu8RxJEcaM0h5;l zBoDFs{P(}2%>BJ_QPQh3gl|+u;{S+g`mZ&X__1iRk~rf=5crIB#SgelWXMuf7P$wX z=4hqqbCqmr68!SzA24$jKI&8iR;oA&haBLFQpyB*L#YXp5B^f_3FrQN^NCxYqr7=J z9kV@KshK48CM}g5t!bF5up!?OSoDo@BvX+R6q@~_C)r+5AL~MHkjfuz`Ft*x>OTq2E)j|6n^`)nnk_&u9Q2StGY=6&FD)s zH2Hdf)ub!A>f0~IVyUKv-^do_8&yu`M~Sk>vn{9RCmJ>ItG;fgkG9T>W2=5*=J~pd zDUD0iknYB-gmXv2oojz)YXRy)>s<`AeBt_oTeZcNZiiz7x!Yr%)iYTUJgC zza4G9bT_UY-66DumX@ilN9wj|MeyoSoV~{0{t+1+a(cWOYJZL&7>1Z^@$X#WoCkjN zd=Y8nA!Mm7TE`&a6mn@rNx6l0w}|9Y-Q(HwL2#?)xHEWFYP_ZNEi&%nD-{)x3ijCb%Zv@b6JcK0nZOvcVeIFpjbhSm9YavwO!``kO zKtl0tKyTmr2cg-q;fynCr03>qv~^gjI5}97>GW%~{kvTsF*BEtSA?Uo>HE)i;I_%;Ygv7HaihyJ2ewxaE!M~S8Q)FXObQAX zl_CoN(|{%G@i+nMl9_&~nnSLsGbepyjMlU5TJO46hu1aEM?1R6VJ=b9+0tACnOrxk zA#0lolu`UB^?uL%u&8JdH$NGFf2${!WaR{nF_EOLyjVcxJQTND|Qj3_ijA%iQ&M3C6b&k~Exw&A3TpM_@tY z+SHzC$B}`xYS5Bk-Hr)Zr!kH5m;caK=&vx`SeEWrm)Cecb7dTAsDhFBVO=b;FpACp zsh{%Y9|G<@>LAk_SL+*@9wMxUMLV~a@68>IF|~TGbF$dV8XwcPAKm(as7kLOEPfqX z?Q~h}%~D83Wy)>RX!MpL$5ej8D`<7C6xY(wVfy_$IklUwwrq(P3&plNQz!wl{vV6r zHrm&K((f(M<>~Y|0TDZpq?rVWfA%CGJo3mw9(t&{iwD_Uc!ii9QD$$OTAw*6L4zZ5 zQ%DTTUYs>DTfZscw4coY4nFmx+TcSDYS=4B)O*g3+`>CUhPiTD3>N6z!Hm}(M;E0| zLKSV;e`5cok&%IvEjEm$!`hoB`;K;sr=V$0BtIqy5Ga@1IZ&>c{eY*dTItQ%{!^(np0>JfznkmEC^mM!x7oCuj zDJy4KH@8c>idn-qQAK72-H`DSKG8h=G{YAf6%39ngA?teR&|Fl#hEVhemfTu41s}e zP3H^f49Jk#3WdCfgG#2YcdM3Hk(oqg;OS@M6^cKMilFHUB#h-YMU-<&Fs8xh>VyhQ z&t1{^QY;r_)Zcp5o{Of?A?sMG*+ZSQYSa0xo17{@Q~u^T_~O1aW3 zDQUBbppk?yswEaOd~XySZ4svD4@sZ-lu+WAC(gtY-=oLIb{P{@Ny(yV#_#`^Jfx%H zT_}QCpDflvY6}RV{hHh)9Lw>J*%^rwetno8M3j0qZ?;&RT+lwWN{+JDK*C#Ct6~$K zlK`ED%L%gu%5fbh{Np?o8Ik+*cl4GE)#XMIW%PgznXp|Ghsxm-aiN!)Ll!aLZ0x5x z>_MA90&lI8>oOoh8f`^{n5y5j4Rt!eFCb9pXVR}pP;C0QK%DiIWOlviSoEBrF=Bn; zWUQ34`yvV_r{A|FhsjI`34J|BK=eUi?u~wyXwtH)^*&kjgbU~Th$Bh=e(vAWABD_> zVk09*eSsi$(I%pWWi{wA3gbDlz`P(s{?(#HceQDIG2jM zU43;$IAUK>ypt|)F8a~L@amq*)b-jK+K9o!?PVepK#?H(l(hjja9;CyMt&%YhF;oz zL4q0{A2fw1pt*&sfY;C{(Mt!K*+ITVzlJ`bN(5a1{XvV2oa##oSXXlI<$aFf3HLwr z6=_5dnknZa$f7E-X2mD`v5<=HrKW9`S3^BTAVL&W245#j{%V&QeG)zcaHDj?xoOOZ z1(}DH*uZvH8<3%I8?-AOK9GD}o@UFi9j~1Cz%Xf0(C3yOn&2Ld{=flD{cZ!oqVqFn z=Vh^T+BZ|-l3W1ecS8*(_-H8hBEndi%S``c27JqL8>zyw&*%qUitDhzxo(%|)F|)h zfbd{({}&57GqPrK>n0v=iiG3lT4@%IBv?trK(X3Zcd_xVI%3aHCTth_#b|h1g^aq0kxlzv_ zZzJ;diT8y00X%2Ko|L20MKN0p>d{NHrhHK`vRLN|WD!op$3GmpH0%(Jd)YRBI*uB* z#M~fwm=EmY&CCJi#YB*8QBB)hBD6*R;y~Jgrpu)gnrU-=F?rp~>DN91DV@WIUn43$ zhyC!gf&b^JtYudKf8oi>oU2whD&Gl&8Pw&n>SAf{o4;z`nyxT|{dG2gY})C zNV4-2##RjRb$D-q{yp$ZpRJS zLR9zXtFp;na^%3r#cS!~JF(jvRLV1-mP$J}Kv2gw(<}$(>k5x=WgqG;g<1Yoh6H|! zqCCI@lUtYfP49hj8}Z53xs+a+v_RyWp!8@9SRM>5lTyzO-J03sLUG%z%{I=>{KwMF z`2n?>_>2I>nbbJU8W@-0FpO6_VUVkh98|&<&Vg7@fCAKc&_J@SOMCd{5WT2!`q*o; z6l)`xV2B6wmk1p`INWV0ZcZNjPLhC%A>Q3lkC`pt92xlbNH(;gYR)|J&5Y~T3as(C zv}HD<&~^Z6}Yzx+-Tmqb~JcCSf2RGtkTP z*Sx1wO9VzbPLhx@6lGacZn)rE6lE|}EC7k}>Sp=PFif2-HRpgf(Vr@ly=j=zp5SNU zLDGVSx=zwh-gl0Ql&#aLgR8Bs*^MT@&;P!4G{nNKb`V%Z3!rj#Fga-jEtl!v=jC;Y zm16Ggyk}hk0XZ6JqO>eIpU=cr2H+uGxXc|{W+ZY^j3RU~i*tMargSY503#j#g;{2) zPx*Oj3T%k-vFPh+%lB}bPm~v9jAdAlHYaG6%$s#DfN#}LKOdrU;-|ijXQ6_#74T5F zmFmax8IlK+JA5$O1DvqHN5A+fi#s*jEK9%(x47cFv4^t>yF^(LQ{{>yQ2;W!KLQ;^ z+RJUBoLCrxy1(gHgn{*`Cuh{3R}`u`<6Peh6y%yc;d`y6pxMPR!r@YjEXx&lVnN#Z zx4ziD8jkCSlc(J~vUtolbG|q}g^bAmWD!GV|5FdIF(mJW>?m-*gRARHTXfHaTVRsu zx44Si?S#hZ8=7OTB0mt_5m8woZb#DFG#h~-XI`I~%+66HP1z-wk5K;+$W#r^IDINg zOWMGz3aw>nI7~f~Pej#Z*|;AfmOm>~vM_qFJ{nYc zo0TV;mgCa$Qfml>%CWzS=IuSx>ivUnc`(cUv~9Nz4`FG3=Kp0Rg8B;=sHTBeWKJ^u zT^UR#q2Zf(h3}|tR>RUOgOVLX&5kxU@63N3Qm6zMBubZKBD7VNu!Mtn)EGW-Vm?)e ze2nW58HaiPwXpcO$iO+2$6W2lK%_pMJTt>F^WK(SEgK?0RG0{?6>}z9OSC`0p zEtXuDuei0G%~z-@xZ`X}pc{sidm?%-=%}YUys5P7*JR@}3Qcp|M(8H#Zbwr6*2aze zEgH-Fz_t+-!jt4J;u(9c$8rW@rSU6A?Zrd$hGcqAX{*xG!1ulI-Mtm2rhc}xcU$F7 zu#L(u_IQ77e)ux{nj`3Agw3Z5X@2{cFehz3E$Rpl$RR-Bi@i?XeYWWo6J29^e&$IM z3%j&S+o*O3FNtwT4lS~7P_XSi(XV$x96A)9unQs zenNAbV|9vTlBHfBJ(db4n)|wUyEHr<$1mY(ZQQNg#H=RfQ~@@v@E@h+YY2wGBj#N) z)4B1J%E7yUUYqpmnaI1!RJXaDoi0rmXxjtvz-TA(B&Py_4<`y5WjPxL50*EfaaM{k zDTQ;5oKG`eiGR9>;J&bbux^IWf2EJ-(~X>sH;Mp{FnNt z+fo7H&tqiM+dlHY+MSK5MHa<>;?lVuE5n;l;d~1l^kt_p$kn=Xgf}^(pGr1MNM>O} zOD=@CPCdi6wM8{->RQNN^x7qI9240lgzpL~CbuWr3za=6nrE@=%!y3P{1&R=3`swS zolg1yW*i({F)pryZtT!{J>@bLauw*49ptAGQH2i?v&sy?Lg9OVPQ=dcIfHz>Lz~FI zHhrwnL})|2%(GiiGEj_2VrnMbBN$`&AqdL3I2p$w&#X*c5fM!a6X=L8Z{?NqY!- zg+=p?jih$u5twQP7Yy{|%UR{UJMnk0lo0-{`|ZKpgD_I$n0fp2unmTKvuqoUab@*D zD%Obm?Ip26c8l99GCPs8LK0?d(wEV_rT@YERGh5%-v1l{7hVXAy@yNnu-eCUlsZMz zg!mH;&c5(!$H~#7gKD-~ngBXLuMJ#s33;oErLnJ%F^x&jVlSL<%~_Xb)TN*+V5Zap zH#dmU05`goHEu=(&U}C+}93hk{nR48>6{PV5{KwdCE zhu@KJ&h00-T8G$MdY+gdRz=~RwX8i2@Z-1KYG7_+cVDa{o~`z>Q0mc!;NL26SY5Ls zfXK=o-G#J^nK-a+UjFd3FYiQ)h3>PUT~ctx>kbW7PMRZ){19B0ZG@X`6tg4K(?|%2 zJJiSCI-=en|0_bw9!+R2++qz^&{ShJz8Iy#C0;HiVA!Fzg!a6Blu?bu>@EohJ)LIk zI7eNy^g+Hcz{figf$6I_g%J(DRl2@DgUA}KM^N0PFR;P1`X7rRex@JjU>tl>&4VbW z5K7enzax9}$J{v(P^cB}w;*VlfJ+G1_#@~Rh`mQ=oEE|L(Z?wRw28dypn&Tj-&aYG zzJ)&blLUn)$5@0~@F!a8cLQ-+lfR>g@oGPiz~N{}s(`<(CLieJe{-2a5H=7C8aTvnw8P;`nM`U0#&M(Zz-H@Wx+29g)30N^xd_XfNX=o}iU2wa zY=4m|7YIKs{$%hB^gfCPf!Rz|bYPb=*G`gz&8Bqzi0k_z8q0I}T6~OH0ihT_0GKsK z2=kXY@|wg+CiBre6K2L8x|A3s@)p@ojbJ84 zkZ%;k0ji-6RxRu;btxGiiljKD?ogtREppF2FzhW$#4DH@tlAl(8yGU7e(Qkyy@Z{k zzsW-JJIyFS*|D3M)LlMtUZ3WYnU#DMMthur1 zb#&y|qE25|$Z5_H`;%FTV&-C|3!?AMT_uvms4EyPFd0LhE=gKItyc5Bz`sLG=^fx{ zOj$NewPV3>#rN6VVenVd#aCYR_+1OxQ1Ckpv%EYJ>EpZ(AGR>tvLfgC_kjp?K`(7m z*|0^&Y^x^RN;LM%-f6S0&=(}UyFZ+)RHj=&zq@JQ#|F6gYW&fS&dj=h-P6aYRNja7}peP+bq30 zVb}7h8!x3%>4No-1Ne$S;`~oZ64*o3cR}6g>h`Eb-iU^BV$h=Ll4;1a1@Xn)&>iu_ zq2I}8PfU+1H=>>?I^6M9%)iU2?iOk@%JSn6I&#T1qb#})g*UjkB`ay#-9>t6MF@B7q$@=#VcWsRvsN;rE1?R*3z2&BT)vs$T;?e=H#U;jJ9hbynl*q7! zDNLbvlH%xU9AHecG!9nr0@Q;9)R=mV#bbQxnYCfI$tAtLFfF8uN`(vn<0cN?p+f2V{axN$wve0SZYkj7dRXMu{beDZ}`0Td05OH{Y$13e>O!` z0`Cuu=+Gll*>Lyr--e{CNurjbT^p*kZ z!QEo=Rpw3oOK!UAn_*~$7wC59BvlAkb*a__BREWeW9SrsO(P0+EiK6mCgfURe!SNPB<9ko4a zvbX+|7T50#MtF2p!3XouGHtO}47R@_%*%dou_!U2UX{$t6ds{B@4}K|nJ?NC1y)zm z*iyh}t-P0xlX!KS$xnh3d{j`Y!`+Cye0Xs0HwRXqU*Rf)Pbrg*qFgK@snojmrg@M4 zFH4wa?C`%0gLl@LiXPFi7I<(~rvtDiU<+v}I4aRJrbtbep+Tm!7B)EjQFMO>T8UyR z;LEG?uO2{17eokkSAAJtQs2GkaJ*=4BD@_rqtZpzM#LJzY$S8%ENTayS{=xb=e`M) z!j)8MQ!Zf~Rs(T4 zGGd4)6BJIwDlf+xw(Aex5gzREp(|p5Soj__8IQWxifM2Ra0A>`3^^k{A9WYpj!oFr zK25r5Bmb*5>01o&dN8#SIpqM!Q2gBNOTop5VIoLt<_hX&rd|h1g4p=NPD5SSSc+1B49dFR>$0TpTq=oB_ zA;B82F!D{FDM5K2rtS_J1=8qPI%t1hBJG!+4<`zze*|jy&KYvQj{@UWP=341SR4!s zC|$!{pdmTie_}B}1Nuq8Td;rJTlgwFO*!tO)tfP)USyGQWj36vfS0HUV8_PG$$b_$8dQ*8Wg`1 znbuxaE0x&fstl10)}SkNSYSIiCA{tN>`Jvhk~xMGN-_K`L}Z$(=%W?AJfqKlj+ zy#e9CY@S9aT;&>HXNs^g50#jSf3J;H$IXCa<{3IZ4KyQiEFOoguP2LFSq(3Ub-|=* z<&#SQg_X=$?n}U}32{SE3_C?vq?;_uUlMMi-%?DaJnxUcn<2o*i*1$wAOP7rGM&$1 zqyXID3cFEPSbW0@$`o~$=_N@DpQQs&+$^+rJ?rvbWUJBCN0Ex8h54fm>|G>qN$0n^ zV?$9^Sp6Xb0KP5#2X`NxJOdy4!1jVT`U@drT15C~6|p4%99@K7Y8DVFoz>$!>5lK+ zH@PdW%I~@;Y2U^%d&zKD$svdY^}v)X3#_{>FW!X#%Tb$UHkt|9f0JC3NNgXJ;@684 zDGFlE0I`pQ55q4pdqL}5rB8^xD@vj!8O&q-jS%QpOje2wgV^nZ78 za)paBOd0)?z3wIg@D&;XkaIQJtCuW@!&Y&O6I z`%4{bSyHEaMEphQ`YOX`8du33v>o|c0H|i1*Kxp0GPrL^;ezPqFxztx`6 zs`a7bWa-uhFZws*$2n9c`lV;!CbfEV z7^)s!BRaq6m!JfC2HKx6rv;=Hn{GB#?%IMl3=V`{vgaj6s|$3r5WYEuK= zxP*G@ysX1##9^egptPDsRib7#xtn3i}Th2_vdIcGFm6;+KjWOrYV#or|sKy{WMpynn?PevHoKa25nk zkHk*9E#9~JUEl2Z;Si$ex?MS_LC7{^X_SDy!Mks|acNU?GD>mR4Ck{CboU0=%quHjn?#Dhgucx((;Y9klhcKO>E(M*Hm39pb@y7-g1GR&) zRi=T>L`8V^ywXmFHW*2oq!a!de*)KBFDWkqBMX}i08oRj^k1~nKy4>XI~B_RN7DQc zPrn7F3_-oPw}HN~o-d<342-2UEtOV#?er(pbO9v=W(!0E7L(N{xd*;dPhXQ`u^thZ zMLv+>@;^6JnHWyNM37)t6o$QMZq|Bp^nIybW@%LA(l>iwDye!HKm5~Hy^Q_Ti+ovV z`IU6C_HpD5%_dRF6_}?NM&)A?7)oh(X#xEq zn3A&FFpl&Obs^u6enM1AwYIK;TbbaAK7ovL0nh>vCiuq-AA|{n=Zm?=1pLFqQcIu0 z1vtcC6RUDbpK?l?S@q?y{S--_^}ZEE+h)=BR1oN9tX%3 zzUvN!uS#Ye%YDY#2=A(8LGSyU#wG@-AOwmU;WoASPGPcXgm!pi?j7CmzXbT6_gRj) za~ryAzP&}ZUIvW##A7y6rs-SDYzoinOvtGjc=r-iCG=9dS@M%p^&Nks)Zyz9&4gb? zuub?70Mx(c4%hIY<@7JET*=EJL(M*r=Cp#m4$aH-o zva-&eAt`;7(5*f*AZ4h?ldHYkdz$LDRP%tl)UUZ8BQ@ovkg8EZNN@{`1)BdjYL0;l z9+%z!?(?aKMZ&=*Gxq2XFDmBA1Rb~!ie%jSVOon2eK$u&qv3Y3Q_bFgU~q#|kh2=* z^!L0wr=S)LE&TEixOPlS=mL}L+4%8lHEJC7Wb&Qz_mTPI7_zb-h<7RwxbxGjsJ5EI zL4m!2vPwUlqIlsKEc^?>ge?rMPxm@WfPx2Nsp4Um*My+<*Xbe*wYO$YErmmzNDdF% z;y&xowbvE9g4+9XaGYGd!jY302gURiJ#H!iKRFINGkH4=-4Rz?rhWZ2c^VQwkD|Sm zaM7c%?ra*EsoVega(Sd^jQ8&bsWT*SxG6;ADA@)6BBMTcRNkHC;wgd@#SCj#hWX%nCgqhc@o`jZ@ob{^O&ulxjRCC4>lEQFEncETI7f1w$)mcDsEfTo;g28>}<_7%mqS$^Lbv(3$pWMBNv&Zm))<2CKj3?Ie#tl2& z6giuiUS(51`LX_g0Q>qzkAtDFOarj`Hmg{ ztu^OMK%FmhEAn6FIVBCwsOeeE4Uly$V^YyOeLkM2AMP^dT?SX{+`|Cia|3FX+(F@*i}x{zRxoGgNd^(1NHmmy7lF9pj!WfoakzLmyq*BXKD?3Ke2;1&=4Jr5?Rq# zY80Z-Ny~$V-~m(SM*Lm-YYH;q2ZR9Y8W!yxh!kV~5|%c2$OD=GX4cj>hz9U~ zi2Qu1WwSccfF)%ARz&Jp!^Q`OJ{%@o`<@EF)H0TiF`kep%U4gO#YsB{1N<2GOS)iX z{wSwcbCZ`IK}Z+M7@DHfT3AX(sWp6;{s7LI5&iARDgdb9#AF;*eDv=IL-&YONauti zO}buvgIvJ-T`izkhpKk3DM;4JpZVP`51Yhu{Af#{^IkKjs1a$)kWB-wwa04ZxJu)s zP3Y`*m2~pnH3DN0LoZa;B$l|7Fc(~^kTsNq09q6>^4r85tx!eT!c)(0I`#TJX~4Ns z%sAZhB=JyvY`;ATxZSV--JcOt=@3Jxg=GEUMyJ2%E*Tie4h^tp_Y+D@^xfY<;UN!s zE7T=^GP~pNsGSF>^mD&NS1{B6A{55hMUXM;`&u*?-bJDUaM0?4mkd?CPyt0O@%8GR zp)=R_%^h|m7!6T}|58Rjum(-F4gZ+;1xZeM=y|OhkJok34ZK6QXQLA(=QnlXum3J? zE<7YCcBc*Ee4X>nuGvcMplnv?U)jow`rvzG-0|C&+?tH@XZmR%T{~q9Lio;a6an{{ ziv`z`4^K1@XoO|X^+uH$uYlKEUy8fel$XkPX?TRkP$4$?_|W^cbFck!rGd)0a6NcW zeGxl2`_`}R$9JKR4@hDM=dWE+1@R__Zo>F%GET>moF>$IsL{Ac|fh z1}3OAgwCkI!@`-zzWbZS4u3I`2Vns4h9-G2z$8=>7D0ezDgcC1cd7mtB;K{1{$-q` z=NIg)Kd&w~0A31)aVLF(WBbWz0C%H%C*TVuf7gc?J>EW-Y5XO`tQWOju_?Aw%Tcnzf6*kfFR@ivCGd&-VLn{al`G!2kCdvq9BmO-WQy?AhSY7 zuE$^`j~#{}t2nb8(hma#qhyYSPJo^-5ca(#H4qnc$u#c|;c@UaY?DW{_J@unraZEG zHcz}5)=BC6hH=|b8VRgEW?xJ=KH^+UllRf|+(<&UilOxHd{I~245Hhqx4ubTNSx+@ zF+&lLj-nz!*h@m<`0Mg4!H$LkKQeePflk1M=Bv-bM3-<@QpJ>p4;+-|m&CnHWPG!V z1Cg|WPfYYJV!LvWY$0h)%mrrfu7!!MOq6LZJ{Zffj5GguZ*e@rqb3pSf&qI-u%>>vhc95Y z0j*!t4@K3Z&7T$jNGtw90N4A9%fW@`CC{DFJlP@ME>__XTEM$9Ul{uURc_4~jmOEv zQ(NUQmZ7+8MRADgx)W3?3jQ8S+++p_E>pn8RfdSx{D*9Nn3mfsUj~5-L$Jb8-~`ji zv!LMdu7W^rBv)E8lL2$K0stM}x5GJhXPf8jJdZ?SE{E~Qi3H-M8zraZR@WcM^%nW8 zr$@R~m0A_SL9_p>Aj+o(yu~+`?fuPtoILSsr|r9Av@B08Em72V6jnNXhWOWlm>cgX zrh`Hg8kF};Oc2#Q*g#AkNcbxY+ed1w6F_pO7>jt}5?4(L5^0FoG7fg>z|C1^u34Au}DG$ z@dHJD{LPk+RTN^dvkFIKc%~Q~Mth~`bjyamgvb3v^c77Xh>HXXA5TQh3q8r)bQe?C zAyx`W$SaX?Kkh|LI{!@jSj5Mb`7qP(J=9o#BU1TD^_J?<;{QWyCe>6J>>8Ep$|hN| zb#dLg67RaTN}dVZiKYLh%P2LYi*_7Em;ml*Qk$lK{NhLj5WDuZvS47-E2x*(sy4|2Wc1cL?&Sg0dVFey8ph`nmVeDKS5mBpf8YtD9?&n1fP#S8Ml;U|$@52OvuD zZ=rtnTzNJ_4fkKj$V0~o6n)LD_(V)ZHVMRHquk_0 zt(7N)(4_`qHZszBNqQzKzy_I;dFQXM#Mx1GM!y=a{|#QBj+eis?9=^QwR zRh+8AQb^TnBlP;J!1Suf(#|8;;W)-b|7Dek(~;8KmcC)XK*sKyN>Ns$X)n?1aPIU1!r%90wRff9l2zgM8r_q&leqg7 zGn#r-DJ?$h;M$q>BtcE(I&ND2ecTwK{CgRU2LI|X@yrE=^18&_FwmF6gPfmIlBXc4 z4Y#ziDFGBDb`)fxEHt9>*U9_&>W9dsl8qXy^~&El=^yE*bIb&mzcM`R0w6HmZUo&; zW!xF~#^v~~MYuZS@umHxQ=Gz%Q5n=N9=wbxlZ)xj6;iyfmrr0kpT1EDhip2-w*)bTT5v`ZK)UyR@WB40mFX(hF7{Dx1+#Gf+Qr8XVhM z4c=ml!{Rr2xcf_M<(6*@Afj}qKh%R`3NyMPj{`z*Hcjm9RI9jgxFO8MdI8L1wqWgB zpkv=x*jd}TaaSr!k^D`Gn(ssZ)uMW76-kda1pR~=iD~M%%qj%oiccKcY{iBHfQb;B z+QX74e_1SFY;LVlU|!@S!N`->f>vFWzRLnknrT(4bgShP8P^C=meCX?q!wsmRXLQ-jux}UJbW$ zLmj!!g#bn_M(hAqlWItG6yXY!fusD3Hu~#xi?dyz$r0sXhs=^^Wb=>S3Z!R-btwgN zt+|i8{(O4W3hxw38a)_iAZS<*^7TXh+_0oBc(Hx}S1 z3AmzH`=Ezby>$I}^9d+Q<$kGJ5b)pNDCYJbOBm!M4C`g?9VjS+{ej1UJ4*PmU1yR; z%bNe=4|3d5a$`rCxj-{0-|rbo^!|d&35&sDr#}hfqe2WiF7mYDwx!SB3aN^xA#RJz zPZ6Z>kSGBADge?UL7{{@qIM0z0_BuJ%ik#$Xz0+7s(#bPMDAk&>*RnEbBR_B#U$RG zFV)r;1-Ue%a3&J`+d&RQu7wk_`=Pygbj(4e?l9U0zpjId*e$cRDlXk!(zO(T8ek4% ztleMRIw8Zkg3={yERKZjThuK~dyB^+4GxmqGgyhwmi}IOE5{cgN&!}H_Rt*z7aS>g zcVk_QsoDOz=sVf%v45CkOfoH7{$Y=9<4Q7F>^D=^{q#khZSAjoH69ICeN&Vq1L;bih5y_pq z^AnY)BN_d282)_IKWXPKQnZsJA#`41>vYdqRReR*m-gceL57u`+$(8)O7KX4g_yEP zu~0+E6=L$uwGC6eSw)!p{m@?wzyre9JGxm+*PmE*8}OWFe3Lcqkuv=xj*1wKtm7Lv z*FlCUI=&K-oNFiAx&sLdorK<(Q1YmpKO1is0SRoyPNwB<_)gTQH0Kk}6;vELg>@(E zlSqVzi_NA;MkZXRp3+pHl;+8>Z2wyKpuxjC*KbTwtz3fbN~YUfRshH~xIhUY`O?X1 z7u;T(T!(RC$Gb2M6b-JRcfB7-L4awy+8&H?=&pru_mKIW6ZyVG<)kM zTJnGm@oSaZ*lVW8=FzR;W)z> zHbjwNPjPS%umU6m>hZ>Iu5|E=1lt+^P6eECP6||5J);H{g`>-&kY5%1&Qw|3z3=Kt zKE)zR;aHDl5aj6dnqEz?!3|^1`nM*Z1Lpd2PP&B_5Vzi{x$*e^kU;aNURnh&*+i=| z$(Dt}m&m0s9CWge@T?+X6q39`0gfc4YHd^i=$*$-{5d?>Bb z?VKT9Rpj-KZdU>FrlID`8I{Nm1+LQW6CnH^{`XO-`@Io)f`i=z%xZ%3E%W2-CEduu z0POlYo_6&Xp*TlAo;94>X+a;CMv^uV0N{N6dBA*wlZ@mb)*Q@}&1s%GuU#{KV1Q6F z^jEOC-Pa5>ucQ0N--&svZikQP7@X(Td=Pvu(*8TI`&fOar?;%&e1#qhG`HosCl4@y z20c&!<{i~7w6N#p_!s?cMjlXJRr0v;{|3IFk^kh&F^yh789k5ylo}+`H@7+Qg>_1a zf?bmBv>^v9ZUfs_fKSNkFD}ocTcy{Neu)Pl5-{LT5T?Saug@RkJ!PNTHFitt%HTdQ zS|<}+Q$>BTq&RwpeP|W5TRCA=jNB~Wpf%v5P zf%vL5^x6#WJWKL`ERfH^k(z;V-IC%Fc69~qU^bncyGh9fVsc9K`I=armNF8k`u3F5 zt8o4@ANC?!KVThM?gDAM$4=f~mr>jxTqM#zK}TVO=EZgLX?{RNso|n9J>M$3kUY6n zrcoY0Sf(f7Vsi>K#tD!fUCY%NGSHg}FAUwiqS1kU_KOU#Ut%wlZdIs;q(BYUs}R+H zchO;z?N~P(iaBQdGWmMxstMx(?d59??o^Hhh>0s~xLK|9x2cPJ~A|Va$X&3+Ip|SS>;V=esdzJ!mw!@eH79gSnGS0~k zIq}>qnSNi^N-2>44$yiPfJr|qUI**5166SY^5wpba77|HZfb%aA%VMUA)dYg$y+Ph zL*ypG*Q@fV7fS*QrJuM@BG1qbmtOiEA0H7<#)@Kj4NYk;3E}5XOg=+R6?m`?AmOuu z2@#!IPiZTpZE+K%@%BLUBl>%F*kXdqJ8)!Bzngxy(LCMMkPy6OV~G`B%2!q=^hCL zo0acj!NQ<}@#nh~UhM_>F}~f!1ZOh;{)|Wu76QI*o+2J;;ZEcB6Kl&4>|nZh@-wSZ z#34D&r#6@(UWEa}9l_(a1T*g81?HpdI~`=$A8(ZZ4-3+N@w3~~m_D%A&&AMhWWCpa zi+-gS=EDc@=nzb14n2$Xo55==cmt34Ew3oR9Y!B2-F4QG_EUur$5Sy}1cQDo8^APN zq*nR0(j|dR?f%p$TF2@1Ak}5wPwxC1l^_px*8_lIk|mJm9XIRD!*}zI&%^iD^cGB<8_CaBmrVA3$-^P(61h#mF1hA zBoFc#S2pwXm399Cv916h`J1}R?s-;at|m z&Rbl%DY|B2TFu>+-nKpnzSC46q4drA=hE`O(=_@u0XL>`F83F0!5Ozop5LG!LSVal zi8p#_TOtwSFSTz%$3u@4(;f@*TJB4(kD0{5r z2@V1+CEfRdnU0`vOK!(Cuh9B)yz5-mNhG@^9f_bzrtN$O?@LLdqoE68U@UI%Yz_0- zcO-rYM3M->35{XGukIw!aX^uMk&gr4PW=$hppk2E<*a?FCGK$$EM2yfb-T%iwdDV( zme!e+)vIBuLBF1EYy4Ycy6pC?;P}$Jk&NNga^Y7&-lqQnoMf-4a^+3bNyK^^!`&`E z&Ja8N7$3dwnr)P`$2x=D_`A|j8^*dlZCywL3ugSCA9re@yLf1rWo}-%h7xbN^YY{ zY@_Zbbzs^9^KCciBS>xL#*b~$?dvw8XzxK?FrL**O;{KI$^Y|<$EEB|6!B$M7dF}m z_EMn`E`o>p$t->A7t)3Jt|5ShkymLr1FjG-HM*7LY9-7f&GZIB5kZ_0x1p*1R$!v9 zKentbbaktPPg3e`gAzH^ix^T>x0)rEaWQtlpD-eOyK(#*NP#<5>^0L1DQ=3%7I^fj zLy%g-8uXT92A+`av^-rgp}cG!O`*kk?q+}>z&c*o7FmIH4lu7+_qPkdvTGi%+ZsLE zN(IKL&X4>tIUXCLo??Lva3taL!#FQj;jXS)_aPdj~56eTa!g!K9m{AJwLqZ3s z9~LX0>N&xFW|@+^P>kMf&p#I=tI~_~h}pm^AU@=`=feN?#eBny8Gr!3ab*UAxfcn|6D>=?Fb%8tffx`@N=%a5Q0mw}MN-@1brDq(K{tajxgcIUpn2Du4agA2S7;`u zKzq&~I=){L6*T>PIChV-U1>Mnqs-D$g5ywd@zfEdxQj7;EGTvB{ufnK-xKH(5R z04OGoj?R>){^)7`V~Der zg?PJfh&WE)DVHzwse&q_!P;j!cG?kqQQ3zBX;J>a$K?qii$l`_iQSr#Jv*U38+9n8TX>hI zp?ymYVJZT5L_s@=XhhC=o>bx{nylL=vBMf_jCc)&cdqWeV!+%v;N*VYWTAw;?6RWcD&|m4Kx)amHu+4Ov8H%;yNZztL(jG6~-Yp2<^l=Dx(s=2F)gj1u zNdqjjQd5%}nsei;V}gUTTZ?`l=1g+mpgTK#Ukk|j8Rs}paVIK4(M$6-kO2!MO&7;^ zX)L&+pwY@Q!2Z!Sqlv>j)>as6t-87j`ou)?0AhkNqKQ3|1qgAFrRyc5UvtP?wp5Ew zq4XuW0U3KJ{EpY^Bo7elRAMhIG9a(zF*j4R`ey!Bitl<1g9_bOBbN z;&f*V{}P0np_i9_z84`m!Ic(><-Tjk$ELwYm6p!fos7q9+(ZDN~Hr`Aq+fZIC8#vp6r z7p;{0!CcuHW9mj8%8X-8N%FuE9*XoIu!r(pV;2H%Jo`HmwiAWZgpY)oKg@A*ndGm# z8~^5X3LL_#^VaCu0ByOqYn2?;J=F5wdaaDq@EkC3Qm@;wf}dSpfa|oQ>F}(slwGjH`G;=mD^5Q^d*ew)&z3eU(W#$sIU#-e4vBd&AZ*R(t#Y z>C-kZfgqiYX=W2)prCET4EnlcP`GSPrJ&XuirZf2V!3};zAuI|;!0>IqHT;~_sFPc zP5A@(A5+`E8?~S8|Kv|x&QG@uA;d-`a9Md2ab)N=0$1d(laNOttH_Thr&u^K_g1-C{k#X}&(SO>v2Vvi0L|)XF-c(iX!i--g3A^KZJ+=KF#J9=h{bzfzll|`F zIt*WY2zr?$M_|qwCQ#TwZ@Iu>MFnT`t0}8V*Q#poA^<_+nIL&UXXXn2Gms~KePDsa z6k5w`UC`!cnrK3f#DV`9nH&lTOx_wdvlv#8< zoYIa{Al~M9nHL+Bbj^f0I<`icu@L-cMD&$aa>;hBMD6nuxkL{?s^uL8oM?T*@$+~z zC*qi`Yj`P^zsV|4l`!G*as#2E9sruCYjJ`+(yV`u1EJK+{#6E2SXi>=T4#isVX||I zm2VF5(0lFcUEUUg>OQ?$ z#iEsTH-f>kg=Exj>s|KcW4WF_w`~`EvzEtxv!u`{W3!}&N+U1Cz+>Teq4nV*+{mt9 zN&;JWo(&ApFYrTFa%o!2^|gZA)aX8emxJbVSLd9xDs~jTE1XejXPt%}PwIEf;jq!u zB-g;|GV|nWZ+bU>MqUiY#(U_!i^VY3_1!^IjJ_d*ul-t!*dk3U`Qa#WL(WFBNw729 zq3i}UtcVwOT8?AHC3b>2Fq)X##1wQ(jZ^YV#0hSqJ+&ptU(ykwI6pOyiWMQs>tU+z z-$lYteBR=5#WT5m$wlf=SKbZe5Av-NuI`h`#zczOwg{OZ)4RnTgzVL1W0c4F9F(Mp z(HCz;bo3;@OF>F7@{Lf#%=&zQZjFlJAtaY2xCk?WIt>*;nl=(7AdTvjsr@*kYb~|A zel=bGvd|jcH3nEovHRbe+e?W0qSDHfafi_mowJXaW!yRLm1=TLNk1`(7kJ5w zDgU}SF&pCJ50V|k-?tX+5@;7oc6Ms=q3FDkFHN|mLVrs2l2dadzt`;cb+3(lk}8K$I>{jY`m1#bw~O}KXus0S~EVPH`H1o zc;(lfo}R*2dFt?S@_5P_kR*tQRM=3d(iREleA75NPg&GF5m~#49QQzNBkF#MZ4lbB z=47A>+_0})QLWA#FQFg%)|UFdL|cEgT58`La&X7=a#x$?C62Z@4k@$p72EHUM|PC2 zHz$=v3g#v7*oJEi1ByEG9Cz{-AKSS9FpmzQQ{{|8Le>#FCCL0=8GRfIU=H@}2LuH4 z^F!c9(EI;GmM^=_jq$`As_+lHLtbL@Dao&Petw>KPcOS^#pMHUeIbmQcAh>=5CxR9 zBs;}Xsw;|3g7#$G)srtGBd6f#7#Y3a*O8&Xl4U4TB**{{Jo43QldW>44~_Djy}k`l zCM9T@orSNL^u;IkR>s8DwV15$W_;xtR8MO>DO#oHOEZr~tyP%6F*o?CDmMOFiTnu# zKGVk;{T=co<^%r$A5&-8TP;EAv;Ykd(khZ=eJ2^ z(T%0zq@Wml8s0SfczGP~{pPXSx?LFgUYHQ7Gn#w)f=}F|Hbj9ID`REmDap~Rw|c^H zq1Db@tU0;-=%-_~i_}aA+ z@!ghj`hu37q5nI=Va?~pK|n`~d2V293PSH1=k7YtEKg`yt7lz%Xt*#>RpREWSYHe& zMbEEbXQ-OR>gdCoHMcD!(7xXevAl}2qIYq5B(&ReTbGYOLw$^S>T;2#y8XGCdGad9 zu%y7grgD3)Q(Ri>@;E!n{ry}XOOwW3giNpiMSIsJwul=!yQ`Mi!^W?_6g1rQEw-yQ z9gTLCOS{ISR_9Z3lbdr+7&kqmf=|KL>8b79dShkx5PPy9JY6cDS?nW{X}XNy=IU8j zcz*XM78ewsEtK1GQIr(i^Nk$*eo{(q>g+^EtMznoBR!mom1exsPO{lHMgA`b!pjoT{&}#w6%qD zf*B$bux|+`4+K7sIx*dZs@v~+Pt}xOy61n7hmrnmfc*c`5T5+2*@aK~3wMfrOj+9O zYpisEo1~>fR~9cxg&#o`GCU|KIzT`H-kmFU)$tw^387J}vNCQdSz3BZqukryP046ZBV4=H?;mRDolGGMss}w!j_(bnSDpEoxS*;L%!|XR;8_9Z4t-(eO`G|a(BgEUox>1dMQPHhsc6oUaI9$|2b%{F!%QAQC@J@W~-`k;vLIb&=4!% zN=~)go*Nn5T*H^eM5Pf6%M6Flkd||8f00+!|IuG=%{v$6C_f!^?S8hS&DNbifqBs` zzF`p??I?|OlVj>pX0ZJ5_q@$7=q}9c7|i!7jDIhXm7TJ3J4`)Uv9~m{)C}=Z9@dby ztgTsjj*b1fu)1F31(F`2bl@_*{tI@QVG9CHlbogE^cBEz@uI3>7MnhEb}gcTSk3>U z6%A}&L6}>J0#ms`T{t>gl&rE0x(~n=?`5s|(lzfY;PUjLc2hRzC>%==BBBL4VOr%f~yjw;m ziWjesw7GL@iiXDgc1relOXl)+Mj4Etmd~LC@yPq)A*s*Dk-`r^s&|e0qJcM`Ah4@3 zI5|hxXzB;&L+qi_T8oOTIUU-HS+-k;@AT>7XRv2^eLf=hs`b&MwwCfk@FeYbX4_i~ z1$*!O)s@RhJXGdNeaA8? z?>hJLVl`{GNrGlgtIxo^#3(Q1 zmpmmX^*(gE5-UeUF0Z+kLy&8cH{1uj%55IAaBRK_#1uL@qsI4BOBdd2bFe4@LF(q5 z8T`8S3d3@^FH2uNYY_>)CGsycWtlG+;QcItIjiw09DS+(+`O6iOf>wy)Ase60%8Fi zXW9YDJnG3bvEX)(bj)>Zj?wgd8@kdydfx5&(}7x_3xkwDxB$HBcd2dZt?jMjjcK=6 z#!talIO(1;bPqe(0UY7rM^cd$A60%G;f2Y#MBBSG?kLIMmD=Nnzhp=cFDa${x)HvG zo0%nB4g{?*fCSOjGX0#6O^ORO!i)_WgBLh>6PTL4w%LS#TyEhYZHL<(y}PIxf{e^i7$R&f9-7yJmy6)UZXP8GZu5a7kjF! zPg3MV(apD{JQNTMfe*nr;fON#__QhyNIf6sfKPbl+uCer}{9Uy`qEIA%tAkuw;1Ss^WB?8i9t) znSO=H(rRZ1^cCdn>fcvGt>;3sCkGr6W(xv*KXk%ckRP^L@eyFS;pqQD*%&{-A{ECU z>d$_p;;f_)ET^2WE^~4(JQt>Id^t*gFuJJ{fU7(QR)iG5$SP9eA_B7X45OzZnj@!2 zJ7L&bmU!MFcYowcQz{6oWotoRmczG~OdPfJr;8umCBC@EhLBCZ%X&a(-OAKE&EFGR zz78>hr^$nr{HG!5QnE_uYpsInjSjydjx1%k{&jMhlp*0-@)PqxYVyMFEZF@GF!E&o zo$CB<##PzNOc{y;slxN?_?QnDiqfk`cVNP+*}cDwFa#>n0x6tU`bWFNhp^!(7h^?@ z4cXq3Wo88KtF(J?B%0ry$?uZoTakm^FI!0;PI(D~U*orwJXME>dPK2zH2;NGChhsY zj*wrLbOZR3a;M85gd#xirYcd6nuSr|_iPEivq@QfrGv3P+xz=r8s&RxOp2dT^-Leb z6=CkKS^5JJ*EGqLKJrOz_*Z0}XVG2bTB#>l7WL?ZS!A!w+zbV}>6B(~XW5f?lvX!a zX*o0$4|_0+4GILKH23QbCHFTWdq{5Aieic^F@tMgv(^ly~-3%bGz2G?MVxJwCvUIeo5%2YeUb~Z= zUD1Q%&-5eu)8vrW|*l>e8}dY z#1-18%nw$`Ix&LL!hDVd4bw&W1!9>WS7H5$FD$b= zKbN{wyT2M7)0oG9FhA7)5AeVK2ksxP_J4STELjR!Voz1qt!Xb*!!P~jFmtiDRcfU3 zzY^sP_3VPWxy-T50zcU?l*C+yh@w^jZ0B3IBZGN9avh9zkW(1q%nu`;H6GW>Xg*d?u<9Y|Y<){npaap0i=EIvY}8 zUCVbVdf!R4KVDSEU;W^=i3MCJyNfL=NdAIIRgI?#=r0BtlxE{*X4|0NJwT<3zTR0m z=+@Ti>K;FEC~7Y%&A_5ZLO*$WsuYLR;Q(T$%6TTDU|Eb5A{naRdW@EDsuFIY6UYqJ z1Z$Pct?(Hjx%(>IQ3bfMz~cX)5^x5wWv~1Tkau0uBUqy#H-rULiEw% zq7?cX+|5)O$_gXPSO~PC5cxqbW1^rx(ySS6iG#uk5CuR0hRvZD8l!c~fPHCSZk&3( zwZMBNp;oAq8&I2ik#P~lyi)ah7v6Sio{4!7H;8jI))SR&w5q6>yDL5)yV%VVskD-` zro&QjbjK>M^53@{$mWCOZEF-ywR#{h z<;vXR(aQ?IQ-G4XLMrBcc3qOA!y+a}2wnEK~d>>GoKt^s-QuUnk62^h5bT{`b z-#~JMB$I>PVF$bpMZb1^7znu&$XL3DaECm?xNB)>gU-l~l&W=ByLFCpx<(|{)Y!om zG-@pli>e+wr7#zjV+5N=^~25t@_hwN+~3#@rk@O#bI{?*Q$c&D#6%N4E7Uhw&WWdN zT)3|T75oVDJ&PN{^DKGKcNs-k{!3tpgr!~75^Oh54e^)ppz7rrQ4609y3x0M`RSh9 zd0}+rmS`MjEoZ%rnVnxDSc5r%aV=8JK1)?f0n{G_j^-K_49YGNLRM!d-hEPieEP$`l8N!bd{{1_4bB zI8JFa<_#6!x`rn~Pht0%aNcXz@JT<%B=#3RtR8`Y(3PO?b)}0_#c3z{wfY~zWj46d zUy((oR)ajYww6A^(zv)D^izTjfsm=chEI1k+YepB?jol9Ku<;g4&5tUz}`%l52l`zXX^X682 zy(3^Stlf9#J%#wZ9VKKoWQYvEi2GOz*5m5sfed#}i6L4HUuV?Q57x+KsIfv{hYIc_ zCpf1^_I^2&>A`C9_=CXj@+bp@;9}A9JS6g8c?xVs;ci^RmtQ4R#DfP%+T1n200 zdAXDrfAjC(eP<86`0PR(F^uO-?PYySUDw;$_qM1F%~&_IE4BAKN`mBonU4tqxBMW| z4XP|3diJcTg``HUJzv-rGlgrpqC!9#{2*3Vd$T?zUx>R5iW5(a>?Xvvjh6eGL8B}? z+smgcaBWXzl#*mE;%3iP_Tv%x6>rq3?5wqum(T^z1%xfYk^98nX;bW_&bR7NS7lHp zbXEcG<|=Ku=wJ$K=If=E<7o`*SHm>lh96mi8U?yKGwQ=+f(af>tQU+yp`Ezw#sGO% za2lp-moG$D>%2{SZa1ngjqrJvQ#;}Roc1S)_rhlx+b0I;)(#r!0?p7Qr_Wbx$7X-G z@f-}WPfAh2<0tLAaxCDdR-WB9)f7H-1kSewCU8c5 zr{8p#+tn~1DJpKgj$CgbsqJ?~Hd5`_(H~B_|NK`fQ+OSlklaJY`Re@l*G!zr+x zH*K51=&xBETt@q?PT6voBDpt_<_YkcD%~6*-&~SHTo2+Hg=mkGmBLuiFH~>^BZ&7? z?%IN~>JaAZek0rS72WCl_zc0A#$#;yi zVCsbI4AqkKur55oF(IURwa{Z*sCYu=Elgs*ygV-rh0<-heL{AsS)X2+hep&)>HU=) z7M!dbRk@S>h0c`f~Q#d7P}VHNICmmOc}LzYCC{)HcZd8!JsrxRHmO21*fr{7 zb7EJ8L5$K;w!A1S;D5k{c5#@iw=0ehDH*kS&$=Wg|Dpj!sH$4-4iG8|Np$qjqNG*d zH99MxP*c?16%24W%{?h))$+H{8oF0h;chye>4Q_(@bbYk&DX@_iOuC%<%3!K5|1CF zl9Jb$&n*b-HgP@(qPK~870EaWA>yltW@`TeLSG3gNL5FP^tP3#g1Ik_%oMX*$)ssa zQ0}z}5Z~NoI%BqT-9Rd+Zn4j?BUIOrAjVy&LsY}CI%-8=%ry9$X?SP_vGXS%5aQ?G!8+$`F<%6UPBIctDy?8;rsOs-F@bMn48C3-hPttE$( z@YQ378K(Kut(c;|y&RGru&H%xBzKGgWg2FD!YTLGZ&T%Ht^oJ|Hy5d^v87kN$IWBW zbArY>xca@hR%An+^Ui;rkEG$VW4ibvy-H1W722@D072d_zjQy4w6J?3US6oidr!$) z1sQvM9jlMFkVyx{0JI&D3}|$U(xy7j!r@}g=_$12U?@4Ezhbtar+X6SWd^f^f6&D5 zJcoE3rvQ!+0qX`2{Ue(@rHftNk&S#KghvGDZ!WvggPlImS{C0~79XCyDR9VrQOFd0lOhdOOFwOKj*n#}T z!Zv$%NcaO>E|-Qoc)$|!I@`u=9ma|qvzIz*tW6nbmSYSXI&jVQOPwmcUI>ccQ>{Uz zZ7+qdWCjS%rhV$LJ?%#yuf&=P241L21P7@1&X&jrNlc`EqZp`NuIWx`cXO45?@uq0 z+)Z9pEaDox5ZE6B83h=MLR!2tv-0KfF;tSADPT`nfv-IOWWDdTNuP<((J+d2pq!<6 zK938AKVo&uY-4*a0@St8XWL4M;BRN&J#uvqjj(99hN|7!6@^`i;6e^`&iq|EW+kvT zJR(FV<*+-l5X=Am1s~X3Z^Z70Ye%02wF>O6m73|(=R^c7{caJ=2o29w{|b`^8=X%Q ze-Sg(5oHCe1LT}-CNgAAK&H?;dt7PbJG@7|J_BR=%*{8r$eO^OM3YJKiU6&q9>^QY zW~mp`4f9OJ<>OUqrOH}a{lX;Ie?;tPep#Rh1=pv(v@GVJvI{H3qf`W(Wv~mDLC)MX z&ArUCY_Z}0xA@}#^{EZ^ucykM_&u5*$zUIE>X1QGfpg-TpCRDlg}uqmTh$qf0DKFC zo?((8{^alyT)EIHIPjg?d?cn>_0Kw&&-3(~}hqxrcI4c_rQm2wFXmxT*IY2zC6%x>58(L+qn9mrNjP#Pm_i3==U74C5+lKbPw*6`5{!n4 zgMp9%&Et}iU%fp7*)8C*v#6u-;PM#v)a)Y1k((N4Ts2vY&kiB+BWv zzKwr;VVBd9;nC)*!ZPiKNJfoRd_9@@s1vE#9K(K1$_PJhziTaK>08=`c$f}}YZITW zvDA+mA*~Loq3bnPxE(5?C$}AIsbeDL`-EFFLdgkY-0xM*AVjs;f^)Y8=RP&N^DXRP zN8PW{UHiPK5PfeC;eZj-OyG(dWgQdi3YXc|*6bD{Od#c9>QtZ5*Eev1WxCUfB-|e^Ssf%eS6dR_lwU#Z|`4(qn>0A3Ev}%U0m`<=l zkE34dSSau>?r)A@)O7Ors9Z;b7i0SyER!BIqCEZmN*c{ zZo&rSo)-Hay3WQ0j7w!6X~9f|zu+e)UqRS2_)3T-rGkEUds}CxbEM<-DLl(4k+04r z>D6(_pojpIs1zwXz!Atbx1A9}ku{7+TDZ(pW2pcRE=ECExaykEw|I58du|31iHE97 zy2^~vp;pY}0~oWV>5u>LrS2XNA~suj){Em;Z(h)pfpI8&v~PqL?S5w5=B?xz3Msou zObqsBT_5;P9Ij1;ZL<9d1K-dHLtkF}#v&8j^3D>CA%enZK~eV90;ZC%ZNZ8>D+vdV zDw|>*J62ZaSDhBAirF9-88zAVQq{diy~dumBb|^1j1fd70X-KguiHHzc zj_A7D+$RXkc?6h=rZO|#TuoVM=G!f#kT}l((di56BzJU8MV2Y5m8smzHInk7Ns>ufx~i9>)SJ#%Sx$R zP@@;{T{|H@EguJ_0ze4xjStH{|Ey2YzkpY+RAT`-;C$Lv{q;~o5+9bpbFNthl-j7A z9M|p~sS&d;5EjD;#X6Q3%I|Jd`Di3w{6)$RVc|Z7X>NPqCiE*aaqT3khq`wCQ`tna zoTug}EBT{RbJvWx>;NfOGPytz$Al9(m9=;yWWKSQ;JdqlDme#xsLz(RRbB+$s%VmPrHgYZ==nT(X7U%(LAZ<1kRq`0C zSe@Y5iB!TIu%||cM_J`weg%>|I3V@535#{K$?iUQ5f5vf}MUgdyiG%7DTpL!P*U9PaEKlC4>= ztQtuk;VOcl_|Tk=nY0K+VT3T3cxJF4lTRp2dRSt6^a-=d*!tO9crwz4g&YB;rU_e3pfUd~WrVB30F zJxP77y-*9^77^V5#Oe|JR2h>1oe6N>Pq7H0_1sR>Pgr)vBgNia(0 zbWW7|xl#2(tlPSX$74sJYJbI9+^(2hkF=MTsTcE7?l2pFja4QS1}yB5Dml*(=+36u zIu#tERdoB%KsV>=T_x|;rvz5!=x^>yj^XDdd-{p@hl`Eo?R_K%sQ+38>wf=hUpx@SN5ze43$N-M>HKWz^M0v2WpaOD=HhH@ z$bW)t%S!4S*_x(YMBeIsRaXNoWIu1v!bbixx^jjh*EGzDM4 z^|?emRdF30bGuCR1h^1*KACpLz!i@B*)Q0>IizM3b|Oi+#&uPE_Y3Zb20EF*a^9HN z$8cmUi|#WNY_jEEYaouk2^THCkSIM>ovV)1KT>;g^`3`KDB%^;}1L5Em;u=AM znB*)M+EtSHV5Evh>LV#r0(f6B|Obe749`OOC*IvE*IJo*pQ+Glg*na85|FIqK|aTh?6p+8^EDHgks4Y$mSzL z(BeYphFgebc4h5M?5u=T!H5zQvC$uFl#UuF%$247U`Z8xN@I-+bBPC+`(qDG-|9e?L<=|jxJVu3I^~3T zCpjxmKX2|}895VixeK>1*Vcv6O;9#VcFk!oDozVW4@02}ioV^EfG(lak!UP1dbH3d zW^~oHIg}unBn2S{aW@{(uhJh0XIJw#Vy!0Imsn|SNF1bQEiCZHn+G;nu9v)5 z;30<+#U##uzfqQ0&&RNE%$xX~kMfujNEsiPfSqy+_SZa|G(yVdfqi`1PPitYrHM1g z^CWX9JSK@7jPcMXsb@Yo+g8k>Z8T>$eBUN=EjWPDNkjd9CvGPm(p{0_etJQQbI@nK;*y> zPpsjjG-`+R7q13GJQLIn!bmxks|9<_V2vEZT9b)0i_n*7nYcce?ALMmD4uPo)#C+| zkS@gg)WwoMJe$ce68iTWxvjsOK=hm%Q_a*!W`l;WXF_^YG)I@QOe1m%)}QyHnvZ2= z_AeqL=nn-!L!IG)wlMhxdrBf+XG0jA2|+kW)i6AL9hw>w|lku zmiHU+Z3V+|seQFNNru|1hT&a@P8km`d@ueR+xxCs?N8u-H} zxmE&nIDKQIRvUS9ki)JDwnLik9MYQFvTq2U@yg_VP*R6UBUn%DP#z}WIO@1CmYv(f zPl%0#K@UXtuQ>*`?e{v9lhC(}00ly@Pmc3sp+;?CQIBusz8;>@Iq{DND&^~{#bzrK zK*BA$P~{ma;hZJJ7?c>L3*oC!FqvgWlBE2sQl~`z8m}UXwnGY;q2bDG7JFLq1xXlE ztL#T+xS2)5<(qWjS}CM#nw-Dn6_4+3sx}oL!5q4y3yD6gk@Dt$jXP4Tq?pWWrt1xv zItbCW;nvV&B$H|ivk07+EvXS7AU@NLU``Yum|cwo$=r>NT|Fs;=-KV(e!-+Hvty6p zdP%Wsvy#dcl;t11fe*t*wbjZswB;Kw) z4h|**3wDFk?6}Rwv!a#Ph~^+vdaF?bt?_aUnssX+2`g&K;Z$b-1Ukcccltcms!6&4 zwgKu?okMl6D=>4JWVt7W{IMYaAIt%5ABt*#cY!a45z?$P4 zi-{`80c&_9>N}QF6js3%>P*>{T-;+e7(pE2)!Df5Qb13Kw~SX$v1;wjJ>M)7#J9!( zNZuWpoF9YSA34%cU5EmDPBJ`jnzBFp5`=bjj?U|WSY45VHn0w?${arD;zZ#4E&kd! z2et=-C|V#P8?sBw?hnN1#1PY_z4l@};{+zn^D5?EWp6|g4_hzzC zkuc08v~ALP!pYig@jgN!mwCGH*K2=Qi9)fhiRTumWEaaIrc)Ih8Kw^@c;ay4>)8a` z62J_>3*vp@jpjEX6rx0o1%;xEuwfUd&I+i&e`A&j)k7FE%@8F$E1caFF7VU4yNsLZ zkS3F=h$a-71kbk_;ySO{B+MbVS+M$Ip4|ix5W^59cSO%(B<4%!{Y}X6MjNRnjHGziUJnl~6|wL5)@H7i1lL3yLgMA=0J~Vjlr!tQ5^%#B|+H;mo|oqMDFpD`Ul+>eU0k z#N#QcIpvx+q!901D~;bR6!3i>*q-WuYLBoXgEYX(=eM~ll9?BMK>!Wb`zJ2Fz?;bc zt`RFQdzY6^%*~$#e9#6d(3~#V`}c7qQwcnj?Hmp2GiYTUaD(tbzU?K@vK_h-rfyB| z+g;BnuyZFuk=f^KNTW>Ig_-m*4J|8q1WXiN1$4mTH4ZK z8$vY!F-Z(w5u+xr*k8)(DA-Hkovq;7F^)U zuFfM%d#1SPB%2oGoqXd7OCA-i;2b8IB*zUHjMHyse&#vOZ;u-eo$cQNbXL-&FLx$X zNJfU1&}p8D)`rUKSm(CN4p~`zhuBGfLiX+?=!VER+0U5-c&> z_9GHZg~CMSc?x~00m2zRuZyJv$kZg>Lq7o7aazL2Swd!tRLL~&vaXJNJYjf7if3@g@~r5FgzlJr|j7Lq{gXW!Rk*}GaPidK+=6p}it#K<#}nWh-s{MhTaPS9|b zrhgIUD5bbb^ROPCpl>cst61t5`cgPmvRMi7BoUOFgRY*z9pLK38e2-3j2}jrD5Nl( zI7!HjAB`=QW2aF>Zs;DbAs$PBBEB^FCwz8A6x{ncGUO5QU+~6k#YQ>YxQ(qNMMR6Z z+PyW~#pMJEcH^q+11=gh1}vTFwF4PML_US*z0~N>mqcl*=-Xbp%s%Kon z_yuAmfh|5X-5f$M!(v)oUBC_d=Kz)*6{)Ty&{R6G>NubVS-6y{Sdq37YI~zu7Wi_Q znORCU%*QqUh%{bb&EtR~ay2WHbY=UixvFP`&Mqq?49eN>qMQIbN_>b959N2Qi%~{V z+|$=0s?X15`ZUl5?x6^l7@2R|;0VgaVx2ILX*eIAup@5J60|S?+`;t4V32n!q#E(1 zoKgD3=)~OORC|SlKWxwlg+hU}RP9;_E-~7arFN6a*gayFCjuuXqeq8vAhr}^k^9^ai*j#?p#FdYi{tQp_S+g?EYHN zZt;|Uewt@I;iOpvVCIb6;!&6SE3BKw=vB)DEV^-@Fpv&bki;`OE2U7qg249dz#@(L zBQPGrwn|ig6dxb`Uxu#;&v*v8A0JiC)&iX&*$VMKy=8iN{9*Kbs>;l{>rfZd5dmp5 z&66=5?)oMn&fEAJXl**#Gf_sCuMoga*TYM3^32o+qSf^M6&?k4TW9{fnypTRf6Bdt z^Z(KH)lqRY+rC&rkl^la!QI^nK@%K;I|TQ^-642j2=4Aqa2?#;-5mzr9oemS64KRs)Obhm&Secqf<{m>zuFNSUNCQ{tkU$g|89(-Q$f-rXrM)Seg> z^n+#~KfD^@OR0m1;kEk(R0iVTKZNYr4Wd$6#BETtTX&`1ReH(j&F8p(06Qv8jXr{Y zcVzjqW^JZ>U=T}vnD3sx@EBZx-Fg##MtN4D^$J}ockPP{7jy4ihGvSPj(F_dB6eJf z3qq-X2~2Bx70&dNHQNpc#k^EMnhQaBSaxARUuAvQT7yMf5<_J#x6klL^9`+37VA(A zkIq|F6Ngrg4aL>t`jPqeeH+SzNBCG1`rcPPi%`KNv(c04WSEhhSQ(`B>@xI2ZuM49 z)6CrZ(`%T#-cs1C1omp*(@cIlY+m;=V@$x*k^Of`0r{hT{ET0{Ar&V=Qny3z#eN~5 z4`m#o<+?aJYV$Z4bViSfiAs~<+cGw|e%~JL8m%h0b+?gyAgugOXz0xqAUxcBPdF6K zAN1f~Of88_@-9e#ezY82|6QY-jlVLILXfK_6{U~=Bdho$%8u`~f zA}kCO^YHTEa*+k>S_|USgrh8!@Q)kF{}@bCeTkDyCPXf5woeoG4!ciCa0`CDF;DdF z%qA#%vcNc}tD;AoP{Rxr+nDFrveG2>+1GS%VW6Q$ksZOd-Ie%#qrI*Q?ER;)hoXz`1j z)(6lF16(&oIB&tAJknuv5X`8$#P&7;JdVrDAkd!V@W|i+FNg&R=M@iF-nM|ITI>>& zvCtj6pay(c={xmklu!8eo`3OoF(~SVsv0F6^V@k9S!*kD15TpD!2tSLoapaYD~QAYX>p#3K)bk4Sd*E3!yul)dKUhSfBRQH10jvEPwlNLa2Is5F)_TUg;!0`{?( z%cqmEab5CfOJ*st=&S{C>gbXTT{*kJ_ZVBX4uGN~J_L`bJYjna+^gR!c%O92gr@&A z!81>5?9wNE1O$&xjAS6qEK?!L|4bFGhHd_UZ)8jH{S*fK%J1{0561S5+y-mJ?;vTt zeKGI%DsocFa_P%9$X@bvA*9jX-Y!x6saZi0T!PhQnhtUbCh_Yglit%cL)$R;yCf$9 z+2h+JFZ+#RVdKzw=A97mKI7S0>viao9W?5tt$y18W0~~9)-d(y{Cby|2bC*yD8pMpIMc+UBY}5+2q~TY1A0ArJD~Ig#{$Rr;Mi~s7StU# zX~O${1T*i|iHTPeF2ETI+Q_0RYkYG(w%BgjfM1HP|0;&z85Dewi+EPMIZ=3?6w{BN z$yDle*p|aUW$-bfQhK`PJhrkMHSRO?OsX{JJ6XiWDt^%WULv_MSyArz+n%tU?HjkO zo|x=6G*_h$AZXk8y9HVs`FB&bjDpD?On=)jTY zV|xIn;%dY#nYLQosU@SFIYJ`uV4&Yt_dzMP<4KiV!&{g#p#VPvSK6nOS201j3LdD# z)+Q?46g!K%J=l}qdtn@HgKhP)zc!2W1~j{Tn0DS*yRzIhgh$K&+M-o*u7mfGnrf0D zIjstm>1}PpwxtQ$m#~R(h0VJD?w+Acg<7R0TXWo#K5%PT^#oi0hD15@X#f3M@aT}_ zH#_qWu&}41P{$p1oD65O#L;D0_*1enr%fg;^13G_& zOuvx0xF%RjC}wmdw@rbVeVj@iS>aDAeNo1O1|=yo~wnadN# zn25J72_BSEZR=Q8(;jWkkjy~z*vzfl6)~PVzFzYNaVz zJ|_B5y>^99tVb6-3`3EZc8Lb${riZZs_b{%L-s7Y&h~6F0YZD3fU(%?MM~+H!(-S} zq1D;fTD>IMD6P#FuB`RGhnJ6jt0H{=levD;jrOB=A3pHOcs;DqLR}D1O#kl+LN^muha_XAP6sc9G5Fi0mB|kL$HQO%23dj-Zn=}AMERx8ekzgDi!%jqx7r8 ze%}|l1cz@L5xoLKho&Mp`pluv!bnr}y3CRN8!r_-$zK|BB-sK4EDJ-1qw}Cnr%3K; z5^WPdH}po7#SdnSh(Aeumab()U@b^dKKuUer%ExNopUd0t%(4Wu7Y(D^HGtpHC1d$ z_O~+SaR#!^R@Yr|;2t`wC1x)bjv|^92D@WG-#V3526Fe5DwUnASbuc5lT!*w<8L=4 zW29ljVsbSa&!B>r z#~q|v>!u?vX_K7;7*EN?-;ZLcrqg~|3Frgl>i*z1epiS+YEIpc*oS^J1#K4v`{xTX zx#tTbs7e6(443M1h>#!{5WX_qXqULqN}cfS$9bmlR%C+3(bEN4T^la~D@Ick&Cr*2 z*2?9^WLAuFG}W%36*ynW0zZE8`cMX(wlk<r$;AG9G)=QxcAtP=TkIO8 zd0EjZac%_oaQw|%$gxMpglKs^1$vmqDO8%lmOm$wi!&d>@Smg*6{h#W<{PEZl&3yQ z&}u6+2$PgEqKjK|SqE1+I|Ewn7P0CDKprpqbDV6{U=;>wdh;^YLL!G$^M#$%o_x1} zk7018A30cle0qD5*djo2kUOtMpXeED6%Y-Vi&AR|Wa;8Y=dPv<`z`y-bXuZDvCMrm zSZhY4P=XqtkxAaqksN9>Dt5O|_T!W&)WpY2nPeE9EH7waVQOHk1Gal9*y2>;0bd1Y zf6<85hdc~)>1|jd#y$MfYy>}rrg6xhg%%`L{g?x-f>eYT1o5@qoDk?Ex-*omQPN`t zvD8evBnXV5*Rp@&b4*;o z8giD!fukvSQDVym!wtV3^mks6e=v7V_qMLxRIj)TZw(iJG|K)-?Uj+)UnKKqeuoY? zyUB(Bwr9m{xBOsXnnNPcYk8&|I%oEI*XnfSXbP)m-i0@3O;Ko_*?h7Mp z?l<@JDuUk6ECu+TY7ABA=0Sz=9fnU`O|;R=5^x zwJrzz)XESa?)@JQ=ZW6M5_Cxz=W?beIZ^3J6Qt7a=~)l+#H)w)=*x5&6uo|{8E*A3 z;dx}!DYU7vD%7Ej&?J`<@Ko0&S=nT?&H4zX96Z{?c{G86tNXV0`z&Yw7TfD0zd-*B zmNHP_?UjTS&ykW-LOXvnT7((^jdBci9GN7!z*y7`=hP%lZ9{6q^m5?5hJKOIN{c4C z7!*FatWB-98u5%hG0tR%F^NGy&f_el?5{dyq7*$wX^LPfq+~4lZq7(0HN=>dV3AIq zM(JH+L?X2u=jKM!UzPE7dD29kA0i_x7)n2c3vK-z!_hZGNQ`+s>(QULWkdB4eW`%f z>U-9N$0!-fsr7f!bdIgiU7XO}a8$@44O_vI)Pup@`+&6d!7d@5tRgcx$PX`P^(dd8 zaf_dkbs5Um5L!4g*gu~9rwa5>b{x`=mPyoD@;Rij8$D&}YTKAD`{}S#ppWnS&g13ZSrhsBf4 z$Te&?3HG0*x2A5_$e+tA@h{n3ueqkCbypFrJ_s6fBxT-WcXnz%MpJdYN4r)0^F_Vc zH|ZgZ_-G~4YKLKppBD@i709M znrwHUiDYqxs%mJT&zu2*kT$x(yAAFjsP|cT=X&!f+7jt`b{7Avr!pHxkS(|G*fi`Tivenehdw z`1$Tr=S`wpeob8iM;CZmw-bVDudXAN5|m@9>ttFSL1(ht0eW3vFK+B!Jc%E~_sa(? zdN01$0trcMhDO*JDBtRQl&N#U!TP81ro!A618^?*`^5ccxaQ-r%c4(-ut60|dFUBF zo$XP_Y+Vnwr_`VOVmrAjWIlDCGXQ1*{Jlc+|))DroeP0?X zHr(Kk5%5yv7eo5CqN^1_kOC9w@>5F+|Mbq6&-JTsbGszH``zLC36PTPMOl{dsKG|- zs93T~jX<;|!_l?0gqx3E=JE++BYS^<&AnQh{obN%FlpUkMu0~pHcpP}EJO9-w@BiX}pt1T!WPVw0C z7cM{ER~I@$;#QFQ#Q(xE|JZOpN?{S2C(@U-w&0}iardTD4I13@_`>0YJei7woF{x~T+!aIWss#}~I` zP{x|x(023ad0@&sPlrLcUC{w_g}n0EJYN@wF^%4tS_$fe z#aJUj`OUoc{&|@JN2CdSnxgjF1NmFx7a=FQ4*6iicUNBjU_$l1$qGqsCNEtoD2-6f_13as% z?34_3jB3lTFGL?y_+gWLaDD2rm;|$9yLG>pfX21D@6<;p_GfI34)LoaU!_XTRg}o3 zozRoZBj%5?lW%qzm6qmr8W%TFAM$Lqm)Smp9acJ5@C>}(`|Xq4T%%yK$wBSgug$-d z!>3buy6%%zjIw zZSUH^N#@1Vlk4ev$E!f2qX_^1Y}7W;+++V%nh6CQGoM1#**oL|o>=&EIsKU|^a&;~ zxrriVi^D?RT+?1eZYtIIB?!@C#6TySK&1~RjXg*iDOfpNn(>unIFB{$pvUA7ibP9L z_Y^e%$p!Cj-GV6U@6v~3%1rOXBNaDr6^y)=g`050j(jMGle&lfM zMKxOs=k>CN%o!ciRg`|*9@w015SN!-IVJz=M`J~`n9B(4!%I!T08MuMv25yNgX(B`hSTm*l1GJYs{4X~4^Ui-yMB1GdoO(197*`bJSp#bOfF8} z)_ic=x4QU0sQR}Ed;^sXlKCY9mGxW#0o;ps6yHB-0h_w=SAH5=d=u{tujK_oV~G~L zvgKp{Nmh~_GeR=0{ZtdI42K?uU5m#imWY$=cQcJJj^ISwztxL3ij9bdqt>w-UYbfp zEam;9qBlEl8Z6WfXf8iZ5#SeOes%yeJTQN2Eogg&IbOv+#1z|TVTui>GN?b0abvBg zUaX0};V?}r))47)9qbSM!Phv3sqlnJSr^>b`8ffO$)C_S%0$%R=MDmrLENXOvxy-J zHkt5M-e7G#=k{AAzGN45*}v+A71N4H2aSfo7xqgxqBu0NY% z!;7V;X>pU7&|njqmXg00iQ1eNu?70xkWnEXf_$uJtog|b?$6|+AJ(XbYM~$6iSJHu zIJ(7!L{-%c4t1rA1sz($>j^1eIVN#s={R3!+)wh`2_|jzVS4vJZYO6^hdj~=bRk~cxRr7IhOS6=OY}G{!`uQAVl3Z+}1iBk{ zhQg=BvFp~hskDHa-hq*Ld|vxY!k%91<6ncVE41z#{;u~iw?E5nlnk;&d1qc00K~ei ztw5|zh?2PQJZ^l@^lIoC1Uc~3)e?6QmaoH$(46uBk_}#_b#qkIBW-TUxk(ZZ8Nro) zyK)i6;nS*kU^I448C4PmX2{aZn-(6sK+wJ=15@n zw>(v7z%#fguP|v@95V>dJSU1W-q}AD6vYxmIVkQ~5H?^{vElnb)*JRd4oJm&RHsvd zR@4JIzH+lKSkrM9#UZqpB+j=kJzeJOi+C2pVn}9TitNImAHTB#-Its0 z8vZ|7ku1RwtK&^|_7w4NMvZschlM}OSn^HZw}weTSV_#IbYFk@>kj2|8(l`{Frq%D z4Q1<6plc$E)5u*VO1kG!;RJj?n2Q}W`OJM1>2pe)b-ZgQ#d62M56Ln=Z`3T_4#G2_ z38_#=@fU%H9%Nz3O4{O+O5ij2DkP*bb7P{?N7PGO0>aucr-@fj-ua?R9h<$9WXYJUH(C?<>43gK4RS04kxjxQVfK~ahK04 zbMv6P+o#746eY5j@MZba=R4v0vXwu?{}%S{fI^?suhx`Z2W)M4q$a z*KlAuQ887NY_-EO#vLGD% z&h^rTOOarhd}FADc_XbSIJ!%Pv34OVHZ49Km@E-*G_^%qk?=}Esv+f+g$gs`llH5u zSIwv7htJs-nXT0&kI`Sp#QkuTf9#E3>~OLT4Gvc)O3btT;SReyN(F(5@tZk2)XT`mg>S|7{%$qmrpR;eQt2KUkzj4J@pHXrH+kS@JpR^IbSf`k+diaTP=2n#xa4-w7_%0 z1lU`iS-*}BE=C(2+w-kTD+%TTEG}yOoYWfW-ZXYr?TKLKKe)%|{WPF3evZJvL`u)d zm@3b2J7dP?G?3jOQ2aylq|f`kI`A3MUC#*KZN^r&$Q4k;|BXEGameHAKz>UM=(yzs zx%ce|!z$+up{dy*Wp-tCjFF~>I)^=JpLhC^SF-zdA8^0OUW%!M-|0Hu>l%DXj0SZL z?!T`qtgpUdx=b7WVsSXmbW(sbr@<-JHg43b#)gB2j#z=xVQj*3YmL9agxLxy*1;*P z^DQR1)iG2nc6Rq8KB`lnwycj%$5m}zqzw8H;o0$ptCj7JK-JClf3lw;>mKdj!blMW zSvUNDqJJ0kWN3ZSu0{?LNR6G7FXi@}=0jWVS1L79wRLwPKGE+P8+5SitqSqv zK~v2rC?iW!{&ikZ=u2m%p=BlT(|j8zA3#dKT~|50k3P7osJh|KVKdmp-I!}3A*r=A zCd_Jl;$zx#_!!z%OJ(U#ezqj^b8a={efxU`kXlmgo%g5A%xJl4PXpSNxGb>48%NSM z?L2Db&rE6Tr=Z^szP{SjdP#RE z3-JW$%94wCWrpxa*DOUxFk541=CTU#*&p1L{dV=;pj~8rm*8aocHh*h#0!TE&4R3z zoT)*^5nC#dfs)8qu0&hUmNBKyAMj^7Ov&>nIuMMKXO6V%qEqfE<)WK?oX_#K6#FZI z(AOnOy*i=WN-Z|{OL+0FFeMoGryX;32O|ol%CZM;E|F5fiZd;DmD&fwK3dYoj}+_5 zMFf{}?I^7&amy)DwO@a>Q70*l`;<0%1x$Z~wWhRk3EGD*(RL&Jw+QfS!aq=@by_j; z4^%~nvJ$BTl>I6GLxm7i0O_9wKIs{%DzunjpIOdXG1~d4m5SMlxs|_eo50mvE}fg} zSzD@LdqLJ0-49+*MGC?zz223F=di%W) z-6&)e7kHIWn0@lqh1RLuilhor|iw@@R8p{TEprG|KHv9 zKXxFJI3!QhK-sh<=bx$of2fK=F^uRSS$>QFxQ`*SP2K!!j9o@6$u|vjJRD!AK5$St z2YocuWCW~11*dg}bw1_!^!!EnE?++=FbF9dhpMNsuM>Itjd4+F2ITzZoWt?tsYw-n z>AdVM+MD90ghf0;OIMFlX#KJOmS^>`o$@>7?g9*)R6{iuJkMnQja@`A=dchY>_Amt{a2-1*MPJi z_G4$+kneTcnpV}@-u}uz+mkmE|I)RAL*H4{XoPLNx5xAIE#b@GXb&71+|dJ?UPc<7 zq$O)BKL4Qw7s4HFZ7(e61_gLmtCW}s)U0Z+b=pkp{FyO5WB+MZ|C{gPFLnR*)qCo% zEG1Tu-2eDTi~he4{Z~y!*SQ#kAD!^^paX$|HLh0SetelXsU0sc=UMX3S0=~!6jT{-siO?;_G7HkKv3-1 z$Z5^_x5(OmNCgBL1<)=q^AgG?$Nfbw+;lhH0DXk{n>c)u7U*Z2jOn8|l zpZ`;q3M+PC)~eonYkC}#{t@tL4f}s(Zzn7?@F9v88hBpx*WtUNGCD=ctCv2^6B z4f(Hfm+$7i!#~9TXQcW6eI#G@zquxf&@L&@2Y=-xR|C?U1qvd|A~vf@|FJ>%zhyfR z_T>Ll(O)jD=kb>Vjeo6x?xf?0z5hi<{6+8~kGu}~wT$>U|7F@_=>}K`LfduHhO^=S zO)4Lz{_aRpzU5X3{+-1#nw^QGeNGBDxAWTJ-O%4!&Nzl0Zm& zj;iBk8rlU66z%H#{N4L0Si$r4yzTN3_jMI~R4dw5`JExH_Kj5J{i%89TJcIL;vH`+ ztAGLIH@YmZ!8azDIh_9WW`6Jq%pA?jo9~_1y~bbd!r6Y+B>3P+a8LH80Vu?e1#;Gva%UHf9K9mh_JWU` zq#&;dv>pS)gHFRhZ%|bC=%HN$qJHl(umZq{SH7a;!xY_gmSr5?S)z za1u4oSH-G)^?k^ismGNYM54utqQQ=p`Y9Kb6fW~KN_D*_3nsThIxuVLwBo3|f;ofO zr6o|r+uF@v2^kug`THDRf{{)fu#P&t-xCE5oogvAu`vEy`UZ>+UVUmyqqG&S_H>TD zRzVNz-HOO(kD#vA)SirkEIBq*x61_1M+9}pM=sh{s4!Nyutz^gV*05LWqjP-u0goa zdsp8-)CggH^J5e2btge~$Ad#Zx5?d{AniG|skENoTU!whW@zjX;V$j@9`-mt-sS~g zhLYxcDoCL02J5xo(6V#xJ#utjsT~SoBGyucSBMDaU-?P102s|X_U_YuBR_NaYaYV& zv;|6$iBcd2_eAssL`aKANP8d<-VSh|l!6pA8$|-(2(N0}1DdyMCH&qcr2P6rgZ)K` z4me_ENKw#dB(7$8)aq9J{Y|y!E`J!8>lFCSr*vi+sHd_D`$h<7(?dy=gd0JJ7ehyY zLQjIgKw<{nk1;~%yeag|*mCfE^UUniF|dd;@lV*RA2H9Jrt)Pp;i^_)SaFO2^Rnu< zDmi!LlIiQZ+Wwa4n3dMPV(|=O$rQpAkm&MoY2sXl?7N8;Re|yhBXfuyce(U0^09?F zf;&%w+m_d6Q0mxm#>q+l`FiXl@OsR8Y8nn+ESSz}8MMDsxUm9h%EUlw|wdQ#KFU0J&N_1|6)pY^j+r6M8CU?2B+nvLG^>9sbzzF-1J*B37? zC~kbPx(so~!! z((g02)>N35l#w2W$`!s#saFih9tk9!<@L@Pz6^IQl{lS);V%jG-rXPY@pQWf;ro+i z%xt>L8>;um2hm6O70k*)T(@Ldgn_OjjeyqTGqfbOEPkQ!z*71nC+6pU@QJiH!J%BR z;bahfKW2wiu3?z<;BglehF4LR$Fgq2tgZ9Qz5R9Tp|S7Rw+vruVRV}|C%hV2)Abj# zmwO&x@FA-GlYe`$gKm2*_(sD1&20D(P?>9&@g&+V!XAqUX?yib_7{T!p#Z}MZoT`3 zx&mj5uTvi+r>=7FC4rXI2?)EjD%e%J7}N{dLkgViNdSd%GacOem|@NK#b*VHyJnm< zq~8}AO~n^G1sG~PoQ=G>m2-|y0u6k`DorjqXmh1m7S{WsZ|-g9%V!R|PE1$8`;|TI zK_iYq8ZlOwZ*+DP57-oOlE1rcrD0blHIsjEyWG5i*1UKcT!?Jf1J}GvD|1?~Y27;B z*6kx&spL(eM3nM2RF@bN#PBl&+U6 z;g;3B4Jkc;pgamftX1mnw)-D#={JPzG@l#StvS}8Rqa3KFU>VDEv$96U*l>#*dIU> z{w^NA)Af4Qpe&6wkniV7C?ma?Ud7b8l`R?YO?=?!!pK1GF$KGhQHX*;8~$ykRYaVnhC17me*2d?Xf9XGM?5 zLZkB6I0B`l|D%3j6%jUG)Y>Z5p)pvt!Wp2Bu0Bf+zN`2^A2ryC(XMQRiWiHm>JU(}=Sth{M8Y z6F4z$yr_`{*thB7udgV!c|qR9mylW`^%TYdtEu1$a%gPMVj=Xk45mL$4OF= zS@R0cQIa6YYxHO4e2(LW-xqIYO4*Ik#crJyMp>AqzRO@^K! z)1??%+9|R)mrGP9S+X-iIPk7Ijn3qmTkp>2^E6WBtBpAOVFEJetr?4Nk-xX)B$!;^!-fAn5MG{)B7;^!JlV8^E0Ux}d zb;*LO*~h1@O&?}UXnWl zeeAaYnimh+)68Ju6w@VU1!jY|#(_aOv2n-xw@u;aZEhcE@7JelCwJbTRNtuT>TIdd zBL`In!yyM;!5h&kb`_;1I(%Z84^AiACy-T(@B?b2oz!}%MD=B@VJMmD1RP=y(e%{d z-VaabDH$~+M`ff8CUG)&@U>SAKaYg8wOYiTZTWIdoYfL`_rdyXk!V5zf$mVRq4wuU z15|QW!8dmtLpDOe<@TM5wHqhca+Md~(lwr^I{4$SpENaFc1!G1`U5Oq!t15evVNyY zQq^W9)wh2&37(OV)OQcTaCD{Kfr?ueNt!|(Q1$Sl$T#|W)fSg9Vb5 zv<=Deu9Z{_Y($cA<*2Y_C=BPZe9VBIgn8Xm>JcnR^tMGk!Cp`4iiqIunn1ejd zvz?iCfroaUtC^lBpXb5j*$hG@RECxFi5-Z*2smFAw)8=STV1HL={yJi^(!ircfVV2 z{0jB{HcHk$wMPejG7AsomG>e`j9u2me%t4CkLISPWZ#Zl-UgG=-83Pt{{sBCf!4QXK*nT?r>$6c5Nf zJ%J7m6~fpe82XC|vo@dg&0el>sZ1$H+X0NZPvJ9&DK);@3HGrJS7U}!M+7X*?o6KF zCt+#Y9MO+Dl!KHeVf{7blkfG1j%r0lj;1?h3E*}6dAPUEbmhP&Kvj`PZGyt{-75Bk z+$+f#1{5{TV>Q9^t*mPjvc4$E_@?;TRjx&!dQ%fqCJGfLQW+~5ii!%XjT0@^*dXWm)0beQz@ z?fN@LCtyPN6%A^WGW`5S4r6dwRl8yP%VKylRe6{BF~-KVW=N7TpCAhqM)Gb(ZjG+; z@MG<0B))u@^=aC}e3pWVi_To~))nH!^;-?J-10Gd3Gq;qE92~+cc0I5yrsN4go|X$ zZ?EA44c2QT?U_9{Vi#eN`u9nlR$d9S`0Y% zK)$luJMC>O@j4Cc@(l&&ckG%-54n}MjpQjtjDrBgSP8F~= zIF62FUrFt$@87^=!M<(rx)o&mP(WN(;mXgdmrBbKN)Gq%QA!4W0m=>zi4_Bwf0ffGgr@rCih%kEk{iQY% z*MquH6_l{ZSJ2zsGqWGaU^x)gUn!zmdip2rLY%0T-k)M>t(a%icSF*HohT#I?{f1Y?yK_YcJ=Eq2X1@oO}q8D zZ(WvXWg%{Dg}L7Hirs59{P{JK_Wh)n#r0$5ZSkXs$ip}bIOl%$4fCqxem+(!)(HHv zVeJWg>?2)MdwYpwDOJ%0wN$P3+yI*wUn1vR?4M(-!lrvG77Tqn-63aQA>jdiX;#2} z5$wX(TPJI&%J;QSA6+`*eA}9T-kW#!KFoOO+}A&O%zg7oHt@cPeMG$m%y?;npUz`9 z%a{tpo?Z$JzX_E2rVBmjt*2XKhUC4~&X`!WU&@t&uR-Il9C4K*{$?~5{n&2q%LBTuHQ0$2IhytY@jqX7 zA!QTZ2ifYaj}CZkTK^=<5Sl)r z1>YLf$A_#~g}s(3a{Fcowc!gevylpbGnL&;y`Sc?Krj=SK#qN1{u`GIi(JJ>EANUFsGjMye0>56qmJ)Mf+U1C-9?!PD?POhtZj+v6 z#j+eIuo!}!&pW+`Uku)ieT>&S-BMcKeA+AZ55D*UJjc9J<|jE)>iC-96o6StUTQJ+`e4CwXch+$%_k@g}$rY zbFO!w4_{X`fM8%5`6n+H5_OjpP({genYlZMr#S4J3!ypqYGZ5)^K|r)HywD(asPbv z>UJY6EmQWq-dPAZDcsKYt!L&Y-F<8ad@-*>m$tuQ-qW9x8auMQk*qY;8!B3P;ky+x z1DOK0L>kw5?)L-RZnCt2b^!b1@bW@#AkYWI83fXPGt$n+<(yA_+uyX`JK77}J2V45 zY-suZwiWy65_;KucfvAgK#f_W@V=oiq%}j?Z6WJc?W3D7ZRjp~6sQ;*ay(;y;VjRd z+|6r&eVN;FS~)E@*g8w<%}RROxkqW)*vEQ~ZWiN|_gdyuS0Jhv84R&4HVrO16d>PzDyUb=p*hx@mWE=m);P6;Utjr<&5wCAs-{y{(ilN&-CaATFnMla(A}MShO4il*bN_G{85p|o)K z&`LS0GT4OWl%p@Y&SIoa@8>6RTLAAUIgg{`*Gc$}`?fA>vWALZ6l(ody3|3TU z=2SK!_K>LUFv0kz;zim=xg@=1X`Ko5#$~22s80bgN8Ue54%*Lnko8DH=S?xz&@^lm zYZmhabzpb&QAf=F(2r|b1&WD8tm-cLzFr>Eni?8rnf4|de*7;9u z@j*`-%I;!nl&5_=_>?jgFpJ7Y>Wo4lrZl0EPA@Twy>`AdD0919>uQyuH~QSnYwl#Q z{IpFys4GP(`~0(+jbwbUeJ!zLhRbwNb4{t!a*jrCSx_Hi|M|1QxRXRPLOG%-H&xB6 zB!ypa8dhj>7^cKBJ;Ig4BHepVxC6YeN;?-(%5HyFI%x!73V)xKuxKwPxhT6N9OW`Q zOS=q+jeK>^pAlBeOe-3{$T<9ViirJs+K^%a9QWY3pPVHpE_#}h(WqDYxN!QT);TAm z#jI7UcG8!&kL_fU*n#PEs)9?4l4Wi>PeWgIh0e}eb>RRebRc+UgpaSKY;d`@C0=?T z5TjJ;J2sk?lk4N0%qKF@405vYnQ2aydDVQI{9^bz=Au5Y$b$K!gU51xwL_hD0v>xc zqpr}!ei9r$Hgpg_u1B1|EWD>?rk~EGdz*Zl6x?;9)TUJUwA$KyV8pD=@5zHpEV#Y8 zFa|U$01AfI-`|#dbo)4;yr*1!c2^g6y`Snom_B@&>LI#L_Bfw;1bP2pKObEwob(j& zS+(!Dg>(+YvdIMy z8rr5-@mP|^xcG{SxG>G$SHBVhRSn5&_eicmyJjftXu4|*@|W4@Q5hAXPkJR6M|#fd zb(cOq{f``)2Ac+!xe;sYb<+z_+TAI)AuhVh#oHqG^KG`wb`{o{+qAZ|6~*@mG|5!8 z&%q|H9dS4P#&VA`3Gcn-H;~f+-?|F(IZAl=MShj#Hu6h6mvuDU(GW+#ua^66n3i9* z%tck39w^jLN3DHtoD9A0n7K^tWF(%wm9Zr_3-5S!>^y2Lvscnma&EJ8k-p83y%07N zFX|{N8edplkUxkFWB?7HIp*?g5S8UW`PeX5y{9SC?!n z3m5Ll3s(NcO>E!@{iW>Lt=T@7#cDGV65xH$y0tU6pjx&}!eV=Mu#&qz!J)>d&Eo&1 zSpV>;yd~#2E+*i1%JnF8!f?)ZL^x06a9;RYe?A5MI70DM)Rxdzop3_&Syz`1OF?<& zAl$^wU~uY8LGC?q!c7cy_*wH0O`gh*l)ZP3%`E0KIfA8&^o~0}JZqn= z^&II_U+!}z> zD=t5f&T!ck-O+9H&N%DPqYJHT zE=?4ZljRDxNn|G$6IoR~eW>#7QS)wA{jm_Sn|~Yf+<5qNulpXZiR6EeJ$g(9kYumh z^c|pi5gupcyBuIR^^JNxE&aX*)+@RU1^JZ*T(z{;92ML}RbR|2Uny+s_@V12) zAzJ(WLtW2C-vwH>a~ld_t! zs?Yx{+$?WR6-@#!Dwwav@SwN~xP%X&szf2Hs)7+t?2(Gr<3gOhn-p4E>Y9D1iQ@nA zbINuuU$2zxt7ZN2)R9siS1LiX{mW-Q$qX+C5yJaO2cRae-$OBm1++*@^^wq6`{>x2 zf#Dh!f$Ea&5by9|83C z9rhqgYx-T@6pnf^VkFEqDa~Q%YeoEMqNb8;&rJkKl+1?c?pRAlB+a$?*Sc|0Vxvv6 zoyZ=yP@NK~R!lcaSYDMWlDL0=I+QU0hjnTjAMh9-=B}2oFz_cYrIfJPVM+V{3FVj2 z!IJC`JE{i;Ls{k1r(WkS>WDmMBM3kf>5ny7Lgf zYyY=D0-w91fe@9$T>4@09~Mt2nD3^@GEu+fquCheTcwxuvSbF*TJaT)g~K9NKau!~ z{!y<(9+<%G1@)@NHgCY<_yQdt>;Q+Tsc zLAeOS3J=J!aV>~?P~7J??5O|X5Tn$O?4#0t$!@0tB{Q3W=eb?|n?r%ZU5mk zQlhG!XHpu)85TvVh36xh3)w+O-gDxaB0ufdn~SpJqwIVyOl#<4(WG{{`$G2TX_JVA z1w6IAoMjo6;6>e;$0D*?7={v{=U|(TxjV(XZ+IfG{6D~eS_<$tt(Mb}nI+6(mo&`L z(9mGBuOOFIhs!pSyKqQrssZRT><={}o6ZO@e=PKCo42=RI0CyTz~98?gyEO6Zkm8v z+ech`MmkF;;o3d&^G(lAlQGlNyRi|G;us8@ekryy<{67nxqwivY>IM%<1h7$if5}i(a4ky2%@D8W0kg% z@~PZX0VLrQ(zY4QEZ2ARbOq0WOU!n{gtE4BtnEzQJ0-o)SzA!bhPY3+i%>u}{#?&( zq})#>EY(F1fxP$AM_>{E^gA0BGLKhw zvYI0#pJN0`O*QZhJy?GIsk!j3^eAc#7tF=UfmB-ATvJ(h>oG|mXZ9du-qEJiJW3^3fwMcqJw76CzyCz}&9^$z}?uj@Cb?H^krCUDd{X$A@ zyA(SPDI+?k?_TV+y_!t>HP*MfG@5P6S1GDf>q)hVjw4qyjh9xaa#|D9ql- zb!n0jT}O1Tsf-nLV@>R1FQ_=tAq7lbh?DPV>nvSDHx%?nUzKdoYqB?%YtmGyx7eF( z2&o>q^!**}u5UivPrgv}c3lf%Lj|Zn9-vPHPIO0$@hl@N35mwEUq|A#55(sfVp0^O zadv~`s6O73It{DSCk^fF+ZUt|a#oJo*XZ~SbEPch#?nOTCSLFxgcU0R6pQ4}eb`(Y|O>e909F zB$o+bN#bl`?L}g4EL|K1Jx34;u_utX9GgdNpLoC~zDZk@(6InMf3J~S+~=2iU;%vo zQqTMs{Cxp@{uc})O|Ydm#ff;ysS-^lsA}w$y|`C)Fc|M%7(Os6urxS4bJ*eMmNOu2 zr0?(>&u$0{Fvwxh>(IjyMiQH>vLOeJBF5U4bSqa+|Vz~#KulbhB=diJ19y;MT< z#U&b#%j!}>)|Zl!(L*K{=Ntf0@8x_0*XvHlma(ozP(6PwmE5INuQ3M8RVlZ}7=#lk zA@>jFQjhJ}24yMOCEG;NU&tBIc-uu@{PQh+3CW8iO{KkyJEjHtcLzlXY{rU;XyGzKjWLDx5@uK7OM?mO9{@0rV z{!bMKRmKb83gU6WfBswj|S<9qroeH)CO=z{`kuv~mTjBUS>AYSrc z1U_pcu`}=8Y4RyyWM&d!iu!ovF>^mg8;0E;AcVzaHh?FgNOl!~Bz1{6XrqHR+5lUo z9B6IK$tFy~>dm>-^5;@6S0v9tE8m<*p1FP>Yyl0SoxxdKnG0)~TS>%7Vow`mJ_k$* zg*xtDHp7Cj?xnzBmHX)Zs)cJQa)8$Rs3@>Be<{;bLs!$AFt;|JNHUp}m}e?+4$)!y zRQpS-yKC6H2Y|&o{BfYgke)QpO)(&g@u|S4l@s&qC%`9d!3m*y69U{>-p(4GQN0P; z21Z|0XLLpAqcf^E1M~(`A3DQ9kp9;GG;`mqEif!w1*0?FdQl2R*3Ij>oTD@OCi)Uy zm1$&E+)k{8B#h2hB&Y~fbLC|4j}72 zimc6mc7VQPyg1{Q6gi|_A6`iA@De6PO=^60?)13p<#}&^z;^&^l6;xH$%CGTlWEy7 zdy_{t05VM2+x&K{Wx6j-*`G#(0c(=m>`iTFkb|=e@L;w#X+P_1n;igkPIfDo)I_*q z{f5a45PZ)5bjJQPnnE>E9c3>10nSxNbsp-RjObkCa2{qoRRHvEozY9HQ%(U$z(?kL zyL5&q%Qi1p%(<)DSn`dDbO9poUaIGov&G>G0urL5t4k7J$x1vKl|;ZTNhHFasE!@| z^c)D7UqgY3F>B4ZPagm-HMn0Te<_t4ZCh0;-7#XL0<03^jX&i9LhV5vg9ZtaRZCEa8r|dX8<@3V20RW z@#x%3>)c!2l!Q8}z(!R3x{#Qa53mMcX~1681*qvYarWV0aRx&OW5^seQ4-7%1)9tr zHe1gJ%_?iarIfEuk#i}rm#I&uL|eNYlHJi|+O{fQBohzOHrhwqqO{F&l38uD0rXEN z>~{+II{>^rDfXEZKgL-n#u7pTFt$dY4e+U2U^!bhfO`4^B!OF?-AkQw(3iFT=q zy8%w-R2rk%~Uk8bpEPK;oc zI@PH{8^L5*zZLY~@-vZ8JT~k!*Bi4Ov?a=BYSZ#ZNvwm5kZ7Z}* z$IKT90Vap2XdSW~xwE-vM>mZ#3_w*KVqL&a1w6ahnaUu)ebLX8)-_8vpjq=q^2M?o zTsGz4sv(yp26DFVI^1E2VDB3ngSAboW=B}hB5X(@_6uV_G2}GOjGeA)6GWZ2-Rd=K z+PPWRiq)oB*REOD%5C_#V{Dk2{ghlz-`Y>1DH~>DrdetN^|dRl$ZH=XnD-R(v=g{D8^I9(Iz0(nUD7z7KF7_Hkqm06lveNr8c? zPDDoL-ivt&;MuFZLC@bwyOKk{hv+$B@g&Yt5AO52l`F5-qnvYqnF4rzSWqw3ggn`k!2g7NtgMrm=$|D*>N zz~_I`TYs?6E`ZNJ*ns`QsLVPxefLCB>!)Icnnkw*F&jAb`OQ&!`PMdhzxSdQD&C1_lW7B1-xTOE6Bo07wJg#>P(8~ zgpZsZOa7*S5!OeBa$SdMhw-EKk3z9i3l{gAXI6*Da#?o$swsSuWAkqQU!M`@#Ec35WW## zBj@Jw3I_yOm>l9&0vLm8&{>AFc~bMl+SHLAEE_i%OE*$35ROi`?a@h|kYyOA!=&^l zOVS%J!xEt1UZv$R^5_d#60oIwFcN$)pF9}nR_G!JSVdUPp`SJYtd3zvnJ`)`=lkG? z)oWVm832T%NVi;+{8#U!fE14|0Igw*@h>^XrS-s5%^yGbOUL}4m@&+Vlgf{AK;)hE zwCtic+bb!fD*qB?UVh1?C}HIS?NPzr!b-4P6A zi6H=;Bv;>kMXshVHf4Pq9SzteY$IP2fUwQ53jncTI1FJhD)S5VF+i4s!-|cAkOH83 z2r^?>zXCwMmeY%Zygxyxs(l0Fq>qvgxt8J}HdhQx5QZBPNoHhyhr`?MimYv~NP^Q< zg2@%-A>e5o1tyoCeHlsBaB%>j(pI}W2}nnHIqft;JF}@U3@bwXkQfIsD=eJh z)BeF|*SW9v8+B_^zC7e!2T}y^xCWHFDC@mo?BV>-&vQ%I)*O1NaIU%2miJysCYy!L z&M_BJ29bt}Ye=;XCXXh|*8`u9r5$F_9Qf3{m<7-PI814~{HDN?ou-?QiU8E;~O01O@@M$?dRA(#}tvVafmpTW`%C%fw<>f$i zhP<4Y6+#!ITQZJqlf!pImX{OqjI;({AgdGs&`?bW;$;5M!zc}Fo}M?X9S-KoiX6e} z7@|KzbVganx34J&Bf`dHgn}Ts!*emqKLys@Pv$!-He zZXsE~30S)szZz-aFq*AA$b9v&^=6E+)wz_?$>`EVhHi(@Gwg*%+X>m2yhuvruGuZ(m0*fH)fM-@aUN%`VH^2hs9I6aUn*liae^H(t9 z+IE_P9tA_x(ap=So+r2YrF?~ahrw2^ zsgr*N0mzLk%acEo9_Y_m*?k&EPp~Jjcm0E|*s+=?mS9mBpx`^t!It zaWG8)czZZh)aLCJPH|Y5oqKlHIenjKS_czN!@JsVJfuFV zwGYgm7)Qad4XL8=6~v0~q*c5`&j<4UdL+krBFeh{+j*~5&~?rm*#L~?Y?0AtQPw`j zh0b!F^Xe$KvA5e9LmSdpwl0q2X5m80`J9~JFpf73BMls)JR0lZk92GHcpviZ0q}{f zruLfC!bXj?_5~OYcj*S$oX?te@e16D*SzPN^ z_rL=9{MB9e`G0W%e1868^zS^OEwyP_HC~usF%EcJuq_|M{(7Wgrx+eST6bPCsb*+0er zJgx&$5Z2;Ph${ng2}w~{WOFGdTL3UiQJ9JjLe8drgllk68yc1r1}X-b%gMjF2q7he zq+Pm}dhSdbND&ZASy2}`Lb%z)dl-d4CC002!4eqnG6qU_Pgj`Cr z6ECzMudEyJK|w$T21XdVYE)WwC5JfklbR?Pp#Yzq@+l$Ut-Z)GC{(Kh5T zkAVjz36>&^q4NtlKU0B~(F(%gNf26}BrGfe<1+zMAqi7~&}G73yfg7*JOp711p$&Z zyib`eHazzV7O2O$2|K5SbYNWEy|l#&d;%~dJs6**Hz!h7Aw;JHGe=7y6|t&1jK{}) zsp$riITK%cRs85n07=uA);PvwBMoRk+f-lR({X@h?9rJaEJ$T!de9fQlaMul%N}eD z)fd&9GJwbVOkZ*Ubh@tUYF<+2 zi5<{WrT^m`pG^*HYtJP>F`V!!fCq==S6RRO6_!jILcs{O=djm7FL{1FbfNoWJ!lc; zHzHha%;C=lxaQ?~{EiWz^T;qk8URW4!Wn>aPR;<5PC4+MArQ{!)7f0~gbBr7CX5l5 z8M4NqXni9kt2iUlgocHA?l9L4I$0yefDoG3iPRB#h<59`pob=aj|PXz2KTBKE={3; z^GjHfFe8f<4aru`Wn5q<1yxtJo?490R1&~SBuN`X9Pl(xm!{fK@xqQ)7N0U+e5`I$ z@?nl^$EG0A{~P$^(2dT-V1n!ddOrHoHoCJT5yIP|9PlFOjWT3aUo27hSPvw6FhOSk z@HAP!i+d|5H-hmwYUd4eD1UWj^yL_*!)2i*HQ$;vG5#gA0#hd@k1+oCmSN7uV0+RI z70ss@fPoUCJ<9lW%^9DtK1a&<)Wc?v^%EJXhiLxNklz{GQvuMzd5(kSrCc9eQY{rW zA8R72V_2W}zBT{$hkMujo{_ZKn>_4MTH$pQkYFznk!{`+u;u-FK<1F=QZQ5Zre2Hl zIrEzbGy4;J(Rj8m*>!JXj(8dK`TjI!PWCy(56Tzp9RQyvCHAL^?6ZiEmFTgtfcJ_4OW2oGquYB2_NfqcrSQ z=Y-10?gN0#h%1;SToGV?iTz?DD=X_TzLPL|W)2T^1lrVLWgHzacxj*J=e4oMD~_d3 z0UKbThV!<|*HXO+o^+!pGu(AQO+>$J5QI?p4 zOHkc(e%QznXydSM2T(ytwP6ULDUOZ_47w>YY{J}QUAs}a$l=gwNS?g01r6=R$&ucA z!rWv2t?j@@WR4JFk$?>qB|KX15vI?)?7b60+jLyd-1Z)}%;y1o+KJ|Hk!_^kW(qa6h1fl z(!tp>kj4;81)Z6oGs?EQ)fXJO*%WyV=~oq9KUHV!fOD#=Mlp@fkU1LtRDDss>D7uj ztghwee5N;*rW6RX%_BVkj?l()Nj&;9@};c7(%l9e+>NsT3QV}dKcrjuyR-q{_Aff} z=3*eP&!>{d87%QVaV07AhdcuG{BkQSUjl$G1qKhIA55<6Hy85lyS$WY%AjLCMS_tP zdH&Z7l9fl#0S7}wk&r$@#Q`h1$3Bk*1u(9`+Pa489`a{13Vsf% zteqD(a&T0X13*^8*iWp9dlkX~tj`oq_hs&}iYVh#3%s~-7P$4DYkIItl~rm7cpPf9 z6{7Dw1@~AxSiT>mA)u4F_a#odYXIh)xicv0r%saMga&yr~1ge9d7#U)SvHsza`;Yns~hN;Wtp%t_a^9CNSP_pzph5mLRd zj;%ggHj-U|ksKzJ9|m`n6h(ge$;X`XBN*F+TO&k>w42yy4jih8ll@;ExpW4!TSRq! z(jx6ZOB^`m+JJU92X7>QNTDF?;ww^#T()g;Ll_c)3?KCsCM7|Nb$8t@$FM$Oeh#qt)#0saLG=CZrNHNm@mbzCjL#EL z-BXy&mlaYQOd_PQV!uEFG5VN&Vr^qdRv>XCVMoV^Oo}5u^k;;6tD&mYF>cNRj za6_qqNoXdu=@4KOO8HI>af@%I_H6$;oU+_eMPe*qFe14D-SzP3|_P_%8{H;Cs8GnBP ze167*^PfB#hf5obOII{4p)g^Zgcz(6+P50T+fAOpgyxNeX`>;;PC{B;h+V^^0nYmT z+{3_a6F%4KILWc8+zGeDV}x1|%SOsP43rv1*bRj>U@cO`@cmm1vHhxCmoUVmDe-S@ zN)SoHY+1%|Ug1!ZVAIxQ)zYmaHSPhMf)E;&2?voNpkWw8QDsaRc%Kbi8Eq8>K96qX z6gh)QK-8I7-|Z9a=`3j)V6aldN9B%g!W$6r!WTr1p8j;dttL-U^vg43Sn_jGW`nJV+K(UKby zlirlcPpZiSZ(rI@wHy`v)Uey8)X(!q8hH2{0H#2)CNGhO>3rN5hVLw_hZs0XY;AIg zMtIMV7GW8XTy-W(+id8{RP$RAj>RLQFMjfV1u-l+=o)y4vGRly7RWQ^OYKMiHb^i_c;C{pY-YoN z^Asr}XQu}p@L=$32zH;FR0x-?z}~`s2K)iQP$6fRpB#E#jBg)CxEuYkbu8esM|%%X z?-e)t7r{F zFcm8|%t7YiG5xB2+d#L;!er*6je~P^8BlO}SE3}bUxi(^hODL(6S6 zCZAOcQ3kagz$E}%EC{ngogSK7x6i!jRP(Uga&mEaC6~wzfO4I=U*quB#fWxOEjO%r z(-6&rsh|hNLSb>iCc!x|4JKp?b0SRtEiuN+u)$)4wc1)A0Qlta76l+vopAvw4z(Zy zI->(?oYVQmFTh^p%b~e<>qeLw@Oh z0bJJHo+TJdrK=J-$=Zy+=J-U`$=F0_dRbOHTUL#`{S3CUxu}9o?JQms%a&n*f4w<|SgVC*$O%9?~D|OOw_Oj7$nX zz0a{X-58k=#_OWGtOrpB<|>Yrxt_Y1&o1VolXor7(jd>CO1nptf;#O04Fl+q%f{ry z*)fG_)~^9R9$y-{hUr$&T+N(#Awx@__1mxcByq|`D7KZw3Aj#Z=*Ch+QZS$V=#Gjy zABQMSr;Vcu8RL9(kWf1DM5_UuGVn3qzb-L-N0AaXTT`DW`du-eGCEPp*00DdGgtP%CMe@@2*r&6rVA#w(LPEm{@*0w&MAL|9Zf2ctOZ1g8 z+8wN^T0CzEc&)iRm7PD<@MzZAE+M)dSDYvj@*}do#y#YI^Gpas#y%(=!$5mySZGI3 zzTOtSvX6A^*c7nNHg+g9vc1Oot@&XAm{_Yk9Hs{iQpTv%>#P$26TQ+0+;fsDLi2Ek zXm619)Jfl~!^Huh)%#3}w)picG00kEd*D%BMaI^zP)8xvvDMQ2oR>V+IEcWkwD#xmEE@{q^|UrI*kjSsM8 z6J6QXA}*SWMZp%Lv~=1zD@tup(J! zPSKZRq+tVu!zuUafzK2`5-FDeJpBOwPVC+Rd*T2Pbig{^$I;PaU+Rz8M_9juFg62- z0_D2btD|$$DM=nS;>qhXIel|3H^mN4tGEovV>kjl3lW)-g#DakUyHGa2je(a$cY=! zytp_7f|}zKagd+1S0#L&y-wdX{Q!X~QpW&$5Sh%J9dl2WQJcMkkm_py$?Hm={KBv; zS8K#dt|L^I34V?jWsREIQwuK+Og?%O`Fm_T{ka| zF6Hu=bSSwRPH_k8nqR6zKY9`)*ClxxSCC9fqKPyqc#ycVKLO^k#aJ%U8MTAi&sp1) z{b@q_*`v%mw)>dQgoTI-`8K0zqCb+PxWYlFIX*SNrx$4hL^s*PTPiqtIOSj$S;Cw3 z!ofW1lhUDn&iZsAJ<^5cF=+CfFUss?BM);h1&M<^#Ja9-E=Of+Ic97qor6BpKd?J- zSThZcXb~_cPH`{cP%2L5{RCS;=V2EHbn)^`u1}9}V4X;wu=gV1aZ&3Put!KvrNOr4nwK9E7Cx%SNi#UcYuteC zQ{nnmY1~}P+3Qm|AvE*!HTfvM60675_TrjIzfRCjjEVW_-V+~LaP{@fl)5YN;#33!p z0WV6ZowHA2p~8D<=Jqk14&?A!Ltm!y?%EDpkW>TY*9y}wVcwlSLctkF7m|pRuZlcZ zn*i9WNh*obR}S)AO}iEHFjh^DtD~zL-g-5`=C3)xt1M6qQNY=D2@iXPFekzodkWeB zd?-|^F!0BHDK*E+xvB4KPq$_L>8@;`H(6M_DL*=cF&_8AyfR=W2LtjVHlh4ghGCQU ztH<|}q;E(OJ8w>-k3 z(gmoZg&I`)up=9=4Zwxa8w^as!h__hRPk$gD*EiU0TOUOJS~Vi4LbY4d7y(0wUMslg+`JF1HA~CDbsM z<}i^2P=HAl#7Pn4(By+q6KjBw6-P>{ zj-)BDlj1P6f=;hMqjXQ3grX@!xnd5^XUxH4LZo#rmH~$6bI~3rU*hg_$uJKyyU%2q z!{tiY1*pZrQJ)!ueA?mQNFL7`p@G$b2gVV)OQ?MekUPd{DG1oAjA#W2#~kp=XMlr0 z9LV)+0A*MUd2stY%2@*(nzFWeBLF6VW8B_JMs0aT)mZCF@^_Z;pHejtw` zfHJJ)(_UDLFl!uuP*ITFDC@H?0#Ha>Y%y5TIC_@jLD^+oR3hZSQjl*9$k%2(uPE+u zT5Q7tB)s_k$(c3Y zs_lAhxW#6BQzM_3H zPQtrgim!Yr~08|tZKghvefdtQ_0Z0H~y}vKHckkuq_(ZO8HeG`tUw3f^P@F8b z`HXr0T#}?iSSt=UJcyN@H*Y9uS%|T9TuA~gY8|DBR$u7f;OUd({q`cTk85XM# zAWUV-y4FTXTeTM9=R7R;no{`32B_}wQbYqlCWvDPHy>-mA^RYyUy7%4!MgV5ydiJS z8nDcW%pfAc6!(i#xPq#^TD-!af+dwmz}{uP;gr+4uTdA8>vICgG}HnrIHuI8*@7ii z!_iW4FH6l0TaC6ZhcquDd3qVcas3n~<^c?}BYB^n$hWzGe3v5)kDY!b-|mwqgfBm) zS}^-)2q#1cV1Q(zJS)uqLID7-AT2_GTb`tJJPC?R;Y4JO$*|U>04^K=Gs?nNMs2V3 zDH)%v6Xu0;2s`h3ZZXr{-K|v>>08!YnE-3N4Zf z(CCX_!8PX17^a{~tAeiJ2zYaFBk#_tgb-sT8J$@NY+aSd%)4jk%id~~NC?0d^u>>` z8Cv>6v8!&Ea9&t?Q}o7$xbNwVIt~UkiuDwaq)VTk;#hf$zNp@m0M7hGBE&y6flO1Ma`Q%(K zzJ7-=_pyPCFuW_wqZ*DJ50M$ldRB=a+lap3 z;8S7gmI8an!{SCT!i_Bpa0mmwL(#uldujyUssp`U zcQw)+=$fW$+XSb8u4yU}08pQ^k7LqW*P6LY)--Ef%fT4D*7dgMW?cio(j1LAs*7#vg{^>_dF)IX_k4cz^+xroMMlx-!0~)+8b@&I~wi5o;HA~Iw4uei!LAM?81yyLRA@n@4p9l3D>+CG&;Yr> zeWV$2L8?$2W5PaGzkE*`s~@Cv{8p|C4LJv7J#W|mvbB=>$Lq_7z^Ah8wSXq~n~6rl zn0)qtp<^Nh$I%UJ9CGARta1T-K2Vy)FP}*dEP&6?WFS7qV=RErkI}22;F>N%JrNrQ zs@p|QEDzphH2nqiV77@Uw69Xasa$6FJR?yUZcu^;z$}m0J zB;Ky&uMi3saX73R5Gz7~q9}(;LaoScmtIM*(Nk;&zy&_2^av+B|NfnvefQS97Y0r# zj6DDZLrsSsNt+(xC^hAjabN@2h| z;8TBs$pG**z(AaP;9FEi7!icKWU;mgG#%q0OEhxy8T85H(qz#;SPSxbI6?aKFO z6mtNxRHm2_7zB>s3N>=L2BENN0I@wnLLS2g-OgZG^vHA7D{>Gok+ZKTZ%FX}mfV47 zHxErO3({0rQ$s6ZH?#nHVgB@C{+Lt%{EKozwKqQMi$lVckVR3T&5@8tF9;)kLHIfE z%P=8L=s7Ha5R5j}l^P*>)glb8))<`$0eq5?(-AeriPGpy3V=PrxCf>!GPrR#AV1yd zw{OvzeYto~=u{o{ zAQ_6N;X4H&P~(>zRu`|OdiYPN9erm2x+4J0!>SVjG}(PA>AhEyenLLHEYxwvoUVjf0o zVdRzKgz(YCimX0+CTlM^us>&hFc&l4iDUt;SaRC}~ z(lm3`k=m9t!0}^H^cMi+Yd#H)y!_3;XB1#T^(BJdxHyogzNp>|DDc7g!sv@u=$*O= z1B4Z$IunjZ(G}GfSR&-v?H2%B4!@G>(YJ=#dPo`AgStnq$n~Y*m!C8iKG0kjMFDi( zal!-%04%M+E`Q3ps_lu}6(FKmQ{$4tH{=V>)o`3N$sI;f6OEUgzc}P;xFnU382^!B z7xy?kD_d+p5ef~KwN{1s7e3Ov9(>57XYEaly$g_Lwl_s#fUc4TV(VGT>`zG?V7fQ4 zQo#Hqk8hhkp6yQsfVb0<&+JW|3CwtyF&mr6Rvf^G2!?Tu{j82tsVL{)UC7Z_7jpW| zh1k}&#rN`s1d+J1+R)J@){P}}v_&x_4JB`BI5}0J^MC_|rUiUP2|12xF|uVG5dhLL z*8uh~Vc9Sf3-7+g;qkrXkH|&IdVh+uIq!e@~|K*UB5F?~lK9h6U*FPMT<$L5j4?4rVvmtgEye{UxHv?OV9Fd7Md8yIQ zXUw@BCW=;iw?KEooypxa0T-gM}nr{X}QyYo>t+E zfELYzZLjRfWuje7aS{bTVM2de;FE<5mk3@GQiK4??L09$qk6Lni+K%w*#t;Zof%QF zc}U?W)f?57xoB}U1C|4vx^$m{p%_KNfTbEntkF4BL|OEzD=O+3El(r2Iup*W%MZhl2{q+x$LvCPuUz0EO8jvgxNT#Wcv_P5{32T(q z1_h6{o=IvO!MT@WZmBrCICC#yHDBzr*B!_ufYznvvIW%AG~09vhhuEo_1CL3A(B1?nckO6sGi03@@`zc>nGyA^sFqBx)|g z87SoJVgq!g{GR@LeJmvkg%)8sV~HaoWliQff9W!49P8v6N9VMrf=0ddNH+mLn?kf} zlGuF#Bbq&9V@ozk0kMs&LE`Yj+8x3W9k713sd`@^vZsiSGzR31Wxo4K^%G2Em*Oam z0Cq>+IHuSuI=OdJJN_13eN8HsnjDly$b=lfke7AC|46R~z$f;YL(^jX%xenkcmnV_ zjak5FIJE#if2NxjfBFo1U;%u727~ai9$^7|eypDTov*98)Qn&dQ*6lXTp~Z@G9jfD zc6n(P%h`UKg1yOxi}B}2TzC`PTCj-Z^3B2(Y(a8cRzh-GQIH9i#bZFzJt`Z+6paS> z&a}aylrULXgXeE7sj#WKsl*+B3e)P@Dj}$_Rba*h@FY)RaB0r9OAM(iq)Nr@7EIEX zR^0Z-s9?A%$@O&-`mYlrG?4@bMUn#$aZ|8r06t!0WWL37N!apbHz?Pzqnyb#*?Rd* zw!e5Lo6jE`FM855GM;WbAYdzpCmm@N$WeyDu7x0qu=qNZhg9HmE|?_zrXFf#LTbfs z4j2^H==I2(2Y%4T2p!=e_b!pZe(MW~ZodKmrU*~mL4G?2CzK7BA-63qJ zE0}r@UfN=-t)>`?h9DA(q=j_GuzvMl)E?4LeazRC0$Nl0M10E}E|%o+lNEW2&OGCN z3AT{xOb}7P>=I*H_2r7gY`Fn*0KmHi&^Cf4Y;;9+W(DRZhpz)%nKI{Z#eA>{NZt(=e`;@Af1-YqFvAJO<)d8M$fSD*c z^P=RJi>GiJaDYhy>?HwBdNe8EXWs&RlJ^U%_kF=9?~3GTeEd{a0OeOWoGcR_ zw&Wkn3iBuH9ZKW;M4IQ^i;$!O4B-n7J{JJq#Q_Jil}!mCq2+aQ0!Ap7;FfhH!+}ZJ zo+|%Ys5|f$2U*YoKwS=^SL8uGxsnS)7Oe#ejrmvw}AMgZ6KO`HlFtAwoq@H*Ns zst8kMF5bL*WBN7shPl`RsO!aGf>2z6FyhTGc4Yh2BjzJvcO0S;<{(A<9)LuRd|fJ4 zfQcd%>x*MRC90siV2mZnlbc3N=o|-bWqt&vg#QB$mJYs_%E5QAB6D(FAImXSc&@25 z%P51Pc}(6YCm~cObs&U7hvp4StcoM^$P}E&06IPeB>Vz4`G%i4^+AA7OJ5>{KRRG= zbY?o!pu zVOYT?Ar$uR#f-iMKM6sE-K~J9hIEf$zu&D2ci;FpKXDL*R7m>}jmugdkpho0qeHQf%exyf`)~pzx<%34i&BF~QlUI*NlOi4-q5yq=%)P8c}_ z%1Sr`)FDtK?A5=rPM9u?6!xeTMOoGYBI~4lNy1F5oB&=Qe@#)f19DJzl&#$e9U{q1AD7UF2p7|*lGn*4rq!3@*rvs&Attt_K+3@XP_q# z1Q7J#_|Q}{{G9?vBl5S6h)Pf(fAcdvtMy|bS*y>50Y43lf9LYJSRfq1^bNV z1D5s`LhkHJg+p)$>B?oE{EWdR7@tcx)v^*x!~q7Ws7o|KHqK#{e7O3(AqAtZskHp& z?X|pp%eq1CmjQE6WeiKo&V2K*H~N+-rii2F3G?kGI=Tz1T_Yi&II*$7rUPKQIX)#! z7{P8R;%KRawxxk22_fCXiTES~^D{u0cJ~^v?#w*Ho047rSq#ayCBpeBZnV1=rNB)b zP?sFvBRJ>1670DW*T>;?j-2825rbb64@Ok_;J0H1cR z>I``TVNx4?L1!W`|1^!th?EQ}-HPQ5Reiax_N7hETj%l`z;bY>^%qHr5~ zMa$BeCeA9=mvi){P-DMy#Bh);@z_h~4B@y-0dyuHsSr+z0h?dz(tP(4CfpnJMJ3;) zm<>hu0sx<(y-x{zD!Uw~GwgT2^4tND>&*K)^S?%J%MwNO^6bC)5g-^qa&}`)mNu|4 z06-(U7qWJTkoF}7#(uzQ^#jgMoN|`{rWZKmFGs9ctZ{DgJF|QQBj$J0V5Hh&5?2VlE&~(l2el z+BLHAl%iBHgcB6((sj*6S}9%Ein^}l$a_i}l=-?g^ucIcWnDv}OKHin&aAVpZJ>%a zoN3i#*0qyw*mp0(-jKge?lfQL^HEx4qRa)37 z?j}VEwuTlhQaL*%-z4_N$qDR80J{-;+LS%ok=d3lbak6C*+$=kBhnR>zGF_(FZomX z_M|P}pLQs8)yMIz7V5`)nIezm;n!T0PC#rYpr?!V#tAchd5iQKPc~%p8GXD=*t>_~ zhBM%^^!iBh-%^nAI|{N<7vP2s^@iaq+y_2+Y&~C0NU1O(b<31=Vgqd1Au`c45Lxu> z=~G!ombTUbf+@?zc%(Vc*m0`1bLYsJ)Jhe}wQWW!=-Y&}X5OWBWKH7Ka9|3X{VC4g zG$w^d>*_TEX#`gC+v^@ep;#P;1JV?Itk(kg{8&A^xbCOwfd%mSse1l*xbFh^{2e;^ z_qc^tOE;Af$g83ow++zE&f&zfVyas{`7@Q?UlO`O0%<*5ssZSv5~z9xRVn~7SLD?? zsl@;r64G;XAbqh;Wn=Pc?W740#-M|36mk%H0XydEeMhd5IslUwk8>O*Mq-b=Mu0r- zevyM-i5x~8Krw{hk*{wbsd3QjlR&;tn3jWFKT&dYCbv1XAl8KF8SjhLJ@lj4Z+On-VrrR6=@ez9XQaR5#tFb^g$>8eL>G2q@x<>K@f zRJ!I2#;tE(*7iR$0sse0SRTg7nA}feH8Keap(YXqjbI`YhL+j;Gx8UJ&lH~RsjP=5 z5(j`70@5_kUn6g+M%@539wT^udw_Htg6-Z_@~ExJ8f=xlr?4EKz^seYU!7}lk+|N| zxB{%ak$>(}X`1BxxiSR`FrKwQiXBvE8r=b0+My^hEH*&b7n`toHY4(E8IP(0pPf9Z z1jsoDK=jqyEBR{wirjCd+jidlplwBzML;#@3HeSZ9JDXUJ$u?nB7m)=D|iTelH(IEc?9O>gadxl97Nto`I_8) z6o5G85OPXk4M$>K;(EWWXY~Ivxd}b!D8?yloMDkFomYQDz9H1TY*H1x&4pw?N7&iZ zJ=x?SyZHr-h!v_IV=$)x$`b&z#n%+?c>7LD`&3MaDFCrSF2M=&9LHTN6iNU{2ofT< z1bFutFyR?s!loWfm=km0^XT8r@yS7C3OGJhmI>_3Q|976#ci~I%M>8!0l4&SusL9U zEp2i5-g_eJd-N@bkpwKdga?owrdb7sqRz$gdlY{~KJpHi)tImMx3 zT07)W2sn8D4L~h<8uLSWm!HU4)sFK*b4C#wPu@ta46A1hRz2n^jGNSkxL2rdkECIs zKLbSjG82$3ieb1v2=GZxSoB5N70}nDd8UWgHyoZ1(HB8q?67y-BBTJwQT{5 zGNHT~*dq2OhmuRxnXjdIeIsvk`w{l zy7(+ioE`S3UG}Fq;7dsNCS=4qeDgKn=ie*5SH%hN75Kk(iTst^u%MS=7~)|-(g=>Z zvv;R*wtoymoRHH3%r8x;LcTwElH#IZiFb{;NFkprp`?3pmpsQov*9Hj#^v8(ZT&;4 zmj@g!d$NBMm3KE;avHA6?w>ZHy0Z7vWB!YI)D@onn!r^ zrG$|5%9`x3UOa(y`83VJx_$^?`W`(wq$pB>O4MC>2X*@JiqQAK8dciY#Ew%=3(Gy; ziOQ2*iUh85@Mpbn5=}6rD3|6meE0pSy#F5d=Xp_DfIBS`Ja%w2Oq19at50y$Jb`Vx zB9EDGe*(aK5_2*RI4{-VLUc$4$L9&Gs8j7{Xh#KSN@}r3(u?hy+|JLa)cs1Gf>C9O z7g=wvV15Fg)g1EoYDm7XMkpE;mK_*ZUlQ@~m$fk9PLAgTpk)s&k=yyrF%b_(9P+i8 zl`RSg6(=VpY;uZOZ3A3vKi!ax-DOj3ErjDDU?XqkC(ZF0W|qOUN5Z5G*rZzaqnX|$ z0u!Sznwxk^DvR=a@`1lQk_zuRQdwN1FEe6Qojt) znGT@$Twkgz4->+T0~;@l&Lj!1-Q|1Aj^AVt_78JLXGYaSDbug-F3_3F9(yTkFS)W+ zXT*=g3ee2bnJ7*fa%VDQoJ60f-dvOa_8t22{yXLrpx$`pv3Rhbe8|eDVti_GqbRA6 z2nP-z<0?71-yX0BA4mZmziIR(hf^*`__2fWSY;nvWglEx!z$bZ(0Xo0n(pH?86(2FMrxiPhjn9{PL1;lyA786WRj*ZVFu=9mi-onH~a3+^Q@$4n_1I{ z(wLw#XZt5cuGsI3#8qpms2bA8=618L`B>MYtQTt(Tzg6ytLNy9zY8n-Lf18HC?Y}* z0leScj9J?#d}{Vx3b4^QZy39M1JHBll_Yk)AjiHR5EG^%z^4)y2-mV~4s6pmhXCO3 zujTc@H3h4PYX@+5vR?R>_hg53P0#;-_Rh1hjVsC8g$yE)AP9f}m~#*n?b-kT?cE() znWV&=Kq3bsXS`2ckP_v!wYJByG#d6y39=*t=`fT$9b~tPhwr6CsM+fUTAdo10Xw(gjJN=wM~Dy|Nz2SLn~b~DPW%lh95u7= z3Ff9mM&6uN=qRhoIc9jDXn=q69sl+JxCcJ}^=o+l4&T-lxCcJJt^4tpew%yX^Dn)e zU;oW?gDGgl35!(0SsLJN^T$bo4dxSAHDO(-YUP_r5{;uAi@5wAT7}1lIK~3{RL13` z!sgNin}!AXBGt|rfX@klNp15Owc!xW20L@nH&LS<#*Yr#Buoqqj|)Aj>J=LpSPHbs z9-Jg;d(yyl5Mh3}YbLa^K03D{(aunTNfz`AlXve~=g^JW&{AQNT8DH!tb7XacqsCf5-;U1CZqfynORMAun(xeU2{O zB@93XA=P~oE~4}&mc>2+^NUu-2n@%ExSRe!Y)!%VgE&Fz2w>OcRB|8M}CiPiG(d?uD?U4+3yzC0yjT+`c}>0_$1eUmS{wCCk8 z4^f9+%^Ozy5+GMJXC`nLo}%4-VQiQ)!9kvU0gO0)4ik%oKjM&w`3Z6+il9DhsJpK2 zFcg$wI7SO{H-{<19!!S=m>jSqPV-GU;vODX06rsV)~-B}FcF=ZlNn?p7Fggfuo>zV zUsH|$A6koGNKjdSpK!=dA}G5FTH7Bi0)TLi#1tBY0eg)+r9}#0=IRAZ=9f5_zS%<- zp(qLRF2SZGZH0AU*jwx31N@9m;YbU!_Zc*RWuCoTS9fK6{%+ut&+PLYYA8E!A3dtq zFu2~lzk*3i8zs7#@E6gyf}yg!A=7NsrZ=7#25!{TCD&=uZop7EQ-&MQ;_)8OqFWA2 zbHtKZU?Oikjmm=`2wpw38hc>vj;I_48^bfS(9T}c&iC$AvLsuETUdno=fF%L3DJ0B@4m7F<)nchXWYM3*e z-~1YkU8~8LDXyopen`&zH|IuC2jFuT#Cw->J4&PPi#*2gy!e%|(ZRW>KHiv`M#X~i zm~;7&bBb62K50c(&C+7#!pK{^Jj~0>1N2z$R(C#Jqv2D2`|Rr&_;n7>%bXw}wV9h{ zF!6#>gJz@7P3pcJj{$t5pX-5{WkpM~e748jq=5T2rYSAvr7`9w^cyM4O=5Jjut_jS zP0&v6x7V4Q$ajiz*&W)}U(;&&miJmgJCyei{THq4aW?oEdU)Ceq9_OGNFSmLe7Flp zlPw7zV{k?>)Sz?1s(=lfbnny(5+~%70_og?6(6QVseclH^O)hCqS3r_878LrS?1cO zFxj8PWqEA^&;$^K4hf&ix1i6Nc)ceHeeREt;sE$e@$9E~_Up7gTM&6cOb`7;FQy?w z;Bx}78(k7WER6q%Mj1Uz`W4k+tU`F%_>M0>F%gBurETLYfDkLNE<3QNS`=qm8Q9w7 z+V**qXvw=vHsS7BVh-aySgF%7*?K?`idbDlgB7DFZJD&9L7Y25GT`N*9KC|sdP)I= z#3}N;mC-@$(s|(`KYg=kn!+|!)}~&cjQ}P{Fpmc481tnu+o0HjKJeb#6FEVWAqyL` zGQP;>9fx-2(+z;kEx^w?z?+dCOis*JtQ2Yv>n?fQ7?*vR`B%J;~}(Jx8jVWbU!?qGORL{`_Y_f z(Y12Pj6$b zKCdw0X(4SRgq;@VoAI0HLxIou+EW<3Kg!JJj~`gP3ViBZbbazObLn#mI(xJslXryy zYZYXmV4Ht>+^*TkN5Hs&m4XG~rITp$8HMm4VieSA%O8Wud^4tLk>qguR1UU}F(m}} z#5EBm7Dgd}Pyd7g9<`u0MrVl{uRXeXZpYdQGp;pKFxk=j+`p(Y?{=7fb>8LNhLe-H zSSyU7MLV(llzEPI@q~wUyNz`_hGUQ-2DJq1jdLO)uPNrgyopqsVts6d!tNTx;U$>^ zY~Ms4y-DpO$3%qy+GeHaG>IFMiydoP}Zns-s+Mj0FKcXJGBG zJVB0$n4U!&)c~J$1AL~Rzmw$u>`3P4T}dJ}bpfb(!8N*4Z}{b+F)9rwKql>kQ6hrw-zd5>BR1GL0E~UKjY<^{ixL370p>?I<<28y zI6pUr1|o}}>oej)%DF2!f`Pb8JNLU2T*T|N?eNVsY>4EKu({NYlb@EES==9%gH{&d zRteDhQn`3eI}(e-vwb;84`e^p(3BG#8B!eVj~Z6j@Wx6)c)zC5L+ zkm@y?hAfMP3{k+@vaY&zhS7-|Gj6l32CRrC20s`-sOx)^%|VOJK?laqP=8CcHUo&q zugLtvM=Q$qJC1h5-+H^tq~^BoT*3(%UQDx0EJGC@1arMh8uv{^F@9c|KtY3JOE zvBANZX%Z3qUO~LDVts53-@iMP*RX2duvkr?or-r1EIbG^EWP@Go_0V_XK3{k;Pc`1 z5cpK^bAbMv0-rzaoCADb$R1js-{LVq+Q1s-Tq0}YCKhSie@xCiBcfc>LHV4VIj6-k zVRA)tM$H%s0Cd#KP0m=*4)S6gE+XNu8dwPoPZ}X5$uxnK%m| zVHWv-pS{u>Ku>_r^h-%ylHUn8n}mq$UQElv)|{+9qmA(C9Ka_=RREr2)|&W`inw^b zD~T5{gxSzu(UzO!^&H?FLAqmMHDKyfohHc^1EPhgqI)CcJ&NW2BGfazCKJ^G*sIuSVNwH zJ^%nf07*naREjUmbuFK>Q7eqd`|^V9msVl#&dC-m@<+r&!rdtlMYMejQdBSj&}8q` zA&f8DwfHP#SEY*flMM$`1K2D9D$mg-4^XoLqi)X22CjKZyYed-3)?UqF44ZN%bU}d z93|0JW@+H4e)vN5%`4d_TmQk{987Ox8LzkzbaUxML7jNrvW|{^o+>^z{yw) z0}|M3%FGh<&5FnSl4fp7@9r`;(W;Hy$vJbAx`GE{VKX=B{NzXJz{A`$g0}PkIhE2C zWAKHY90asjVFUXVcl9T9Bt&e6Rqle7dp?!tpN+Sjwo4z(^3j003bPSM=dXQDcLC9A zFjoPVHW)J+{A_^YPY8e|!uS-Mo{?EvM;9m}XkkY6^6FPFrICCqmzWgoTxtQK10$Wn z8pc9UzvxInW@IKV4<2&;AEAT19OU|613s6iU1FeUWo?1(ct_jpgUERR zC@;WNjnEG4!YIc|i>wv4Xd0mB5~-f71C48#YDuA#w<%1#WvBJeqIV8rH{<#y9i0MNF+ z5tWBq!@N-taRdRY zg0Y#xfX6Y3+{*(1y{#W)dI7dK&!7UIS_tgr4=Jwxgs%4x_&m|svz_N+dmqW`gCf9NNnY-kB-Ny_8=Ru>JuU8uIBoXK_l$G%$Q6y!CDNzhq0y}5 zSi7N{OkC!0OUGF2?>jVq06u|YNkF<0=uDzuzIp?ED&sT2^Qnx_G;Gi0|7;I|&m`uS z2KZ$CZ{uSB=Apng`vO7#-vJ2)S)n!7h`LssUxxjSw9puAPZwigl@7eCH%NVHU^bOt zW5Do@Ml!(X#5_g02gnuxK+}n|LBUYR-c{x%PlqnB>r?K-8Neryqr?>W%?P&E^q5u7Mi9(GhVW(dNJyz-gsD80|}Kg>SmzY)T?B1M_YJ zfJS4~f`LO(h3=05K9h2qyq1GRoff;g)S5$Qb%if1uqhpp1#waZcE?s_7GnzqKh`F2 zrX0|sb@ej<=5s)o1KCTq*|6DgdK{6~2)a1y=qF(W5MN!RmDED(u?3AVwg3pKx5`D_ zzzZy(a93xduh1S=Yr#Cg8L`u>;gAU6S+7dD1W3xitCVT?hlyHl_HkS68{u)~>1eny zeTgqE!Wh{=!*CtY(oZ{2O@b_ZoJ}+g(Nansq4je}`y$N5TpRa9v_Z?N;ln)`VB{b~ zd>$6yTAa<-6u>i%jkJdOVT}5_1v%Ko$l=wIynAyjm#G3g55OU!Miu&d0zSnT6l)aF zGqxd~7=RE=#>d*a$m=9vdAE2B=y`&!-?^a)kL*+yWv@!xnh)R;`GzNJ#QHcA7ddfgumMw3gST%^ zh*Q~nk5(hMuSHI`06wj>$$A%tExN(kWmyTiOxxRvGUPf}v}giqkr4e&{>EW$80 z5*`4bHO(2o@*KdzF2Lu`J@EMv_>98HG~k35&$56hhh}cNd<#1Z8IWYhNuc-3+~gj` zY|PA0w1?lBn+AOc?)KxbltNN%p@p{;l=V#wbV0|_Bh*&z+K?@OfEnE3Nlp$=VJ0G} zp-q+CMp~-@8pbhS7@tRGWJV@2t6GP-y&1=Gn|*P&umcFPD|HNjjxs%YnGMTp3M_RC z;1$oq&5T0g;h#@xj=1!}`2FW<2}} znE~D8(-N!&nk3gJ2jjCH{te)B4E8OBJ!O1pT)|dSmib#WACEBLDsz82F28gMk7q>2 zeGUm?=HyoiSeT0HxNgODyEPhTgTEp@!cY70B2310LK$Mc;#&$M6h~mSCSkLx9^l)P zmTaFXNa7%1Lt+>;j)va+))NYsk7Wkk%Sb9$Lk@+Pz*~%b*^*dO&+OpTK-|n?!eyLA#ZAcE1EO)U*wDh=Dthwg>d02Ze{h+ zC1?6Xw~eMZ`9e(><}84EeBmZ%$Qyu9Bi(}OUYK1QZ&< zncdSXV=RgM0>JFqD;U@Z%D$#`nQNM*%V!9D{s?FZ@VUr5OaWpNz;XzD9sqg*e8Lh| z#-{?G$`U?62U8iJx5pO&KGF3QA6*u{MZ5^-^g9NWy8hM5(bw+D=A zI<}o(@eD&Y&~Z(_UKc%BOAy_7#x^hD<17WW4aT$wjBTS>^YrpNNI=lVlsdC(f^ovra^= zh0vl?>4c6o7|G|v6~JfehUuMW?&}~iybQ{;Zm*n50g0{HxLAo4x%`7w*`fA@V{fqUTd`?!z4dKUM<=dWItf8#&Y1=weR z&k2~DEC9WLF!aCTe7k4j;vH_vrK)78e5)3p>Ur4#DeM?-*zD;0us1VJ;1=3%s2nRu z<3Om~ADyFRX56r0o>4sx3`&4khtgtWagFP+R&7tST{LETI8w6u<}kkkRt&~*Ef3A3 zt2A#a$14-)O|iKN!YgT{e*z@<8TJ8e0QAYUC3H7I%M~n;iKT}U!Z|y%umQ`(F7q(P zX0>vR>-GSRPSxyGQTVRvb>d{+MZdERyR%h?HB-lF9&!5uplpH4f1!Ym7S@ruYRBjv zR-0@#2jgrVFj4SEWNMkJFKzWJ=!!;ZJsf9o9{^CYH((=TzLSK#lca^XSVj8{_D+RO zMgst(Z`#m6a)3F*fLQURxWpIZ7&^etU~}o`I%%A2AITxixI@|rFOzJLxjH3&s)S_0 zcLjW!HY-3+WeH7f${Y-;$K;E)E=H`}7Z^A#HeL;Y&olDnEZdQbJizr0%g^KtjFmx% zg*yv>7vS0CL_k)^7tNVTN7=Ly*OEU0GSF5D!)f$$+HT=qUMPPCZM%CfLTEZW=gG^)){4GY zGI1%-VS2rQiTrktc6&aJ$3yx6JC^d#+pqf zTWT6##R(nq*_hW)K#P zvJj7x6|>RSF-G%rpundu&d2yffZtUoPt#G0O0HZ4eJE@d;|G8ACxzF zoW#*Ve86*{&%*{f%t62;JFG_qTeE;z&v_PJY@>g8nvsf;eZl`nKab~7pG6JbKpOp8 z)v#6e5J0EuvkuT_Rhbt3j{K4No|8*zp!N@>Sbmf#7@tw}WaDV%#sO+7bW4;@(d;{U z4dXL!m}PtHiPfwG8+a2|8=BwK7{5JPFtNBElQHHtWgY50s2*by_s<>1$6ZOCB_sjx zmf$<4VSKiJ3;5LHMHdhS#!-)g%O!?RFZVKrb)?t2&KjVv*B6~$8+xG*&e1`8sCI}d z|C|#Q3+L%F`lXM=KaV*Kj`a&j;V7$YRonRUFAP(#o(7=1%9=L*owE*O@9lLQ>h=Wy zK4)n+RuF53LTO~AYqS0IRM~exd~%!#=?BedgeOa^~XNC5}-5-F@K8(-OjXZ*Cd>+#wbqjnpz9jIe&x8Pgun{iDTr9nUmsm|5M9YYEog)#gp%SaZP1n%h4d*F1qqq=HyK0emjUM_H2= z%$PP{4OXJCw+nb`*qY3}Wp(?!IX(w`Dq*3Od(L{{1=sih(>)3jXD#Fskj1*@0cU=^ z4F`GZ)MsDEIg_t^2G71~VRoqObQtftfPH-(?Ebdn?*KlhIGbtau4xDnak}xMW}UdO zGG=nq3ovg(=B2>cd(7VKg!STO0yZ<3AqO~LqCnn3v)&d)iiNeJ0-sy3p`Wotz=#AquAwus1BYZb#;OcyV=+Zv`5VSP4na4!|oL`I4%6!ZC_WQ#b?=3#$Aw`_DVcj{uKgbR6miB=R^U9{yTM!V@4 z?I;1-mhCOrE3_(SFJTL@v8(`?X{)3%+uE4%U}41X)V-DOrH_h`I2Que^1-Tu4MP+EG5gK)RuXrsg#H}joJ|ENKr<$%S7)ac0@B970C)!zT@Kq18qwYe`!2 z(=66mbhBaCh4&i_4n7=+yIWpY9F0kClbeDMg_az^96M==`K}lY&3Lb_`%OAp&d)2rk%y zb93qhuFnxhESS<+X>qsIvtq$S?J|$cIiatWJDYpZBZln|I+zn;^J=veU|^sv}5C?eQx#J{9{4StP9N83qZ%Y?Pvjk=ja`U(c42! zO9DKL+G?yZz4j2$Q+11bqQIx+YXYC66nx1|2f#%z#@s}XK3g1>A1NYv=mJr&+~f>x z{4d@Ob4K$cZ$*I58g1^~uL^ty(c7gB z7|_!Tleq!oQ|DkULJon?x>3VeR^$KL^b&TtiGJ^-H&{tGz~9XkA!t`)E8dTKL%*vJDX`Jlk(Lj^t=JJu+WkJ3%1MRJ{YPK^W>QV`5L zM+tz>-#PaFncH;F`1~_h?fz%J|yP%S2W!b6# zXpdGp^sjpDy3}j923KLVRA?!K{h5PZmIr7l6sZ2!Cy20gV3$rV-UU9g<0!@EK)e z;e?rclQSI*x5}_QHD~bukh|;lfoffmD}8dtI^sv$4?_pqK_|!?1(NT9Pc_1!)fNy@ zix885!S}%DUuAAIWP9ENpSL6UpMCv|#^)X2vv&i0DmZZqd@AUvo4-E`e9i`77Ix5) zq-bSrSEPV(Mv*AN_HSxG@P`A=9L!OHA!Xs!!#ET4!r;VGEtDB{DWCagIZfXz@YzlefzRs`Nzm&40^m~%F52pT4}4OG{VecV zya7HlfaRC+68%bL3TyOkfX~~!`H-%^^Edav=l2MF`XuCrCCzxIV_FC8dL7e>w25mm zr$bTh!(nC3ZN@df*j;Fkd)h3PPyvJK5RqNDmPk@DjDE( zh{FYQ&q}+cwzI%Ku+i3b`k&7D1o(`N(CkFM^t;2*eBsiXyy3FmIRGRyU&tE^IpZ)n zV660(NB~e^fuU?CTu$Z>TI(H@x0I2rAVD4A^YF8bPX#_R0G|qC!QP|I zUE7xTPt&ldKAN`u;*&r58lU@*Zx{GXVQzBFbprUjya7J<3vzUI4}9wV{J(%t%@>Uv zjL(<5w7Tz-H^_!`$nE3!U|h2RHVwzMV=4Se;B#z%K~hFLLp0E>KX1VH9Cm7D%eof+){lF} z=bybp_l(a!d*$wb?pwVA_rT}3dT0KE?{yD+{skBDn|g~s2YjB=>YgtBPT$E0Jqdl`7o9lGpYTf$hD;;(2J?b9 zyq?Hml9r+?+ODoFuyjWx&cqnNCr+2jupDuVm+-jVGBV;4 zmz!`=-Nrn^g-Mgs#wNI1l|~jl(zBPi$fNUkh2c=viwQ(f^4*vO=jPG(q<#4b?Z;7A zrH+9KFGf$Go|p%)fo)+TfF_X|PIS@X(IWd$x9#TK^b8dY)pH!MIPF$2aj2Nr#NL^l z@0`l{F4rHk8>8>ZPha1(y4z@pbJ6}BnU105Gi=#ZSN9>4C?$Pd-Op)hQAWsZ8>Mcf zZ$rPqH*s)(>{IK*R(ISeO{+WkqR!iKSeq!@o4n~GOV9!(*L>03QC+j!X6*v_fOB@jnDCU+|Y3%SGQl)_?*W8N{yUUx2u``8I4cX+ug_5h*tON zP~%fu-M7#hdy1~1>eJcK_*88=D_~EGRzL&vuwmau<5LR)J?>MF4VP6lKIgVft9xJu z{amUgkC8ms7)MjIo536njZd`3l!dJtpD&L}vTrm#?`UP*CWXHKZ5p3ntJNJAFW2YR z)m__&6;#p<>bv7x`2v{hFnzxLsUhzU>Cr^jF~!?nD^#4I>zX%r2jX;FIv!3 z{mSw=ZKWAGLn`}CDJ1W&!_ua8Ts1zQ@+@pjsm5o+7=76R74oSod9!n3;_x)Zy@H{_ zT%oepcS%{laazCn#}PC>jlL(X%TqJc67%8CO)K}5cOVsvja09(f?nh0RZjNMP2S56 zK4^RnEMesKRO1t8;U`+$o0rA47&?trx*BRYU}Fw=e2NUnt|Wg#y0?SW~Y(#W=PUg}0Tp7p6PBlIYjPrmijm#U` z__JsUYO8yP(95I%i>?uH4o5#z^*!evFs7mLsjco2%#FSUjZe!s8lS{Ct<~#`6DDV# zkvCpCK{Q{~AWm~;d-wE1-rx;s@}>1Bb4G#B@u>xwB40Fb;`7M&s3xp;sPWm)@5?p1 zu(ukYd#dqC&V1DPM4HGN#@vWj_s4Ta=W~wlt{R`J@2PC~>-`hCL^dXgE^O{b7G9@9O?7wYp#OTp}lc z6vg%Nf$Zm5r>G1?{xh!bHyWQ7#xvE0^+o1nlkrS-VYg;S4e)7aT(hxuV$qF(=WtBx zP(AM5XuPVv_9tsn9nd(;x?BGavrg6I?Nu&J2h<^&kUOU(6L+1}KiBH6z-RLo_=NHK z?gELbe@ozVC86tT+r)2OmtOl?+U1j*xRPX{BjAx3TworgLYlV+iwqmB z&(@JKvEvl!@%m_Y_tLuKH7)OMn4ZG`?DXLxKQ;kKIw2lf;={;qhCLQEJ7K3@pW=f1 zmO!_UP2%pAL*89^B;CODWo90;npIgMV$pyEo!A~Z5y~IX(qF+rzJzv8F>l&1DhS+H zi6#io9lY-L+YVpZbYr*50<}&wB!_!y_8fM{Ihvygj3O~A_^aX&Jal!pvCwseme68D z%ZJ6TZr}~@3BY7;W(@Fo0r2?}fa&FpafShzcn^KmYyC|ez6r6#0Y1_B^pZDoF(nlm&$Z(ZGoMk8u&)Muftn;{p^L~!we zadcN#_vw&NX8abJ$JO1Bt9!$^y8k5At9^N290B}XkoVi)GDpUUFJpa&h)e^4Y)Bd|U>-eb&CEw_smc$-dk|i_P>Y& z#xb2Y{p|LTf?gjjy?!H$QQoy=Y7RN8MdZ9bDFas+w`D$WJtS*$xGl3$cD766>J;TV zeI@EZ zx^Z<+;Od@GSNFEOOZ8>{*45n>6RQhtZ9uGzM~qdEXJmOLf-^T`l_15!_r5+kmvda* z6VKmE@`QPqB29|v_p`3@<@XDDhIe2$y@@AjBx#slB8hWIo zuI?ipzIVlZcXf3?|LMKS88tVm#Mh(^@G0ng#(!aM)S{rK0Cqh+1U|QKbllV3uvnQ3 zUBpO~wHJ=nPnZjz;$9saX?-#m{)G1FPrDcLdJhAbk}~#YO!&s8C_JK{h4{^Wy}YIM|oW02GnXX6rJc?f)#&j3ENZGz`AXO&`ILHl1hjKoivg3hwo7}tDZ zw6$>of4Uk3^zuu{i%AgUnMSYKK(~H4rj=QHHvvCy$246J{??(c>SkPHY^#-va+Ryd zd8Q`kX*BV*1?T4YzJX62J8rdXnJ-o`HlK?adlt+VOHiTdiYuW`v*1S>VAEyuI{_W+5Ohl zJ;lC4UESN`v&PkZL|xt47i`id{sYg(>bM1iq%0HU1v*g)%#Dy&QCD~3-qrm#Z_52& z++TrTy#n{Z=dWIt@8b{efzR*bK7KrlFAsbU0I}?`4RHfxjk1uQn}Ss{<(D66yNNh( zqrpHzqlUTBWvYn7Htyv_4G>F#I0MMBiTW*2PTQV)Eh^B>qI6;Msk65No|U=p$qWooVWo# zZ(> z%abVnNM8!Q)%KLUJ4F`Q5aN1sLTbr#_BC@C*l z1ivDb@u|RPUubosAfW8A7#rgF##4z;cnoW7+BXnyJ4Hd@g_O@=k0AxIpLYX(&ckq9 z6)V8!)+4|kjmHsO>kHz>oW{j#3D?_wg!vC%9?8i&fLHEck*HuiW{=FsI0isL7+vA{ zRg9GEvJ9JR4(5=naSFp!U18rESL3&54dV=Zl*Fl;ao|^v?~w6nhfVLr07_l&4e+Uq zdScRs`!Zm5A9hYLdnvm|DS*$6JjXe^RK4p2_N!Ow8~DRN&iMR8z^4Nw2T;l4UnDCS)c=uEVx>vdMRL%5rg*k;IvfpIIp~4r38QhzxCqe&bT9vpPjRE!6a2 z{8!svDRrV!YDdNC$JFfMq&y^6*vN)_eVCIK6ZPDcobA3tQU)OLLv}ian#H1KEvhnzg ztOLwHi+N=IF9JRtu_YOqdx)$17N&qOuOc3KNVW1w1TCHG18EoD!Tbb}r5*hBQAJ)K zYiP+(^UDL{(frz^S6f4I7=HyKBEd}6|>?E2dXHN2VfKPxo*ED%EEzudAq{$bJ1yIv~ zt96q%Wyu|$$;oq=YIHSZ3T>&!SEUE=Xc(~XFg zxzKIq!ZlfB9(>H&@+pPVn7jGOT=>lbpKAc0YODpj)&QTdYSmcF*+@v`9MiG0pQU^b z@R@1LUIxY|1&PAvfKOLQs04_`w_VbhOK6Olx6oq+{9zI>M<` zfjP5n#xxz*K3mXX&27fDE`$ZZ(+V1)B@NbJ-8+=@?;rS7(uguX#{oVeJs99~8Q^nV z#$6pq4=n?H=C-#b^9tbe&07O}o;eJfaGGavvqao&@IfoEGHgWsR6y z4nx9dz?`nLj)HA}j!D=XI?&$o{ufEXt5Xsz>@!xcw1W#gikL%AZ((i}la1I2WABKp zsDYR2D^nO%GRNsfiVmNw>GCkfbAY-Wals=*DR#8p-{D|hV9_`jrj<^srmN&qRR&U7QIJ6QZ$SDw9WUZqIL;= zHUE}YcPn90_g&6=*seo+oBlk1rmgT+{d4^{`ac}AEwtAx4LA2xo27x!m`E9Xrdbnj6pJ8{0cDW7<48*n*2U0jd8}#s< zT;G*Jck%w^}_ z_s5sSN;{l0xF}&Zs1HpWB~^Yl8%}M9gIQ;(@z5jYG^?RG&S#E#VCs*3!3=C0{tJm|AGa+(jLdL>u>c1%P znFsiE0D4XkF`0j$ny?E7XpO^yR=3*#3))d@O|D=Fq+aey=ItK9XHhO-3M2qN(lC^) zaf%7#rx&(UG#-%$NC7Y>z9uz-@UFoa3TE-U_>L|0BSu&H^j%(JdJdMBrct?u*UHelhV zgky|%05LxR6fSwy+~^X9N?)I0QZ|%ugPe*mJAo53H=sAEOeBsH;1` z8;rMJDQjHakI0{s*F(lCWkn$PBsxRvIQeY0y65NHQqdJJ=ORu*pB!B^2T?ZBFDW#33UOOb`=XdCBq zjduw~(UCdiA?C#9o#p2M+%_p(aE(?M{D3D#!%(%>F_!?KJ$!XY908=Hq{!DS@x97U zxAXu$T?%{><8fF^Ja12V-qsCk`%{h2mxp+^^j!2XmwNz+KzG0R+Yi55 zBW&&g{`LKXQ!}y>1^D9qf8ZIgR%yvKt@Ovt*9v?Vaz^u&e`KyP>mbkMoCL@hEjmSJ7iE!x(=vehtcP*3asDA^(#a(G0vLJL zl9QZGs)LAtM4O1I?9OJ$#KJUYlFV(&YIb$$U^wJ?GC6YyOS>ygfKSWHGjRg8PSHuQ zWIoRhv_ye6uLZ8>=t3UQ7Ok!B$Q3|V%k*G;0(uVTLV!>1`9tDyRE-c@H*;ZHTGuK0 zc^C7icNg-8@h@9+ah}s$?`iR3AoaBvpI&8ru0BCh;Kv)_(}4^{Ut&m5xSN+!x%`HQFn#yYEaq}|#57I@~$Yy+DTr=w@2DY9`H!(Hdl^W82rod-_ zV#EZ6ON|+dq>BL?Bm%pP;p#qYbw?J*uy|>8r`WQ4j=51;KV$fj!%>X?{N#%a|L*Jl zs*Fz^KLW(KOs~+r^JJNhF9km7ia{nL2(e?_*_Oil9m(&!k=*V!?C7+dQIt4k(S6?h zFdFb4oU=LU;rOlpJ?M{4_ znql;bq6g&yfX~o^xM#P-H^+U5#c*?X$rFr$bgkpAC!}rmGDmVsi~MV(f?gdJTvpy5re&M`!0}~K>c-q?m^8yNO~*D}>#MxOy}8jBy0!nYZ+j1Xe#f`| z9{Bt|Zmx=zTVOa^nOLj_K>O`WRMt0i(^B?^z072DXBRWiW z%1T3-&Ai%RZ@O`8cj2__9&sACb}Qf2s!o1g(zb*y*4e(K^Veu$bxh=HJ!x=$YV6ce>L(gaR$U0FzM+0ITHUR*xkG6knm=jl z8sOI*42JIw@Hsk9@9(_W$L3{{mX@tG*h_4vH|GOvGPH%V1naXT`6R6@2N!ZgXj@~Q zkF0Ycn!GA8#Au>&83wvp$N8|=VB^C^rNuKgirxLPn3V9b&;T~4Um z_wgre7DPd;zXSLbKu^{A49WBc8|_DcyN}jje2&31YDoxCFG!12?%lrR-|tId|432* zpO9QkH9tLYigRj({9FO}T#(pgNLE8`*`UR9mVC3o_SE=8z$Xjz0RZ^`_H_l9?DI3) zZ4VN%f2ggtTvLnNgcJSM$%RFkSy`9a6?CI$&70t7g9eBM@Hl)Wx&2p?2jo91y5+bs zDJR!}OXDoupUleIGu6VHlQByTW-F`(UfY?pq<4=c{r*_8M~p3fE8wS13RWlDZE=Y# zuFE8$ZLHyuKKgsyEjG~o;*pfjVZ)rglJsQ?MogE0Nl~&%qsQ}uk(G_X!W2HpU0qBw z7@!FUn|q4ZNLni+(TD*)p8&=zm<>G|qv)LWN~dykfm7&7QQjQGjLF^rpIU@q4C(d! zjCDa?$7K{i;;R9l9yUfWo?OIeOd0iztuv$Yj6UW`a!H>N3sM)gLPky)U*FU2{vNH* zYZ#weP-57*8UM8CqR&5n*5r9_w2&V`kGR7#qW_Ld_gNOuUos$Yj}#;_bCb?dKAzy= z+~oDeWoDU;4|CJ{)2K|MQ8v`Vy@s)plhgt1G3KVz?PJN%>R#kNm3X47+^aU@o6b!- zN8O#9gjT?T6OhIQutY4a0z^IlRC+Ka)3617g9JDICPKxK6+eeJomlz4wC0XrCveEyk!fYBaB+($6fRD85d#52q!ua%I z-p8}Dik9#y0Lpxv4J&!2F+e*yrQ^k0%$VMCjnMq7^XxU~plGtO9fYQsPqmP^Cc!AI zMLt`fH!UQ3+Zpq`b+6F~rI0v$-st>DVQ$2A(|L%>CcrW0O&;pG>%9HR5#)J}t#OXL zKaA4VX*3>r27aJWW@mk2sU~DlWgJVB`xMCDALn?@OV>KioCmnzn2>SC{l&EzSy*R{ zvKSMeqbGih2|X5`f&A}feCi0peITrA2*bI%JU(%Iy=?L^O=Qi2@0Hlm{2pMI*hl84 z$NkdojarLbr3$G=GoPNHf-6HE!O@AR%x!AU4D&|YmNj2AZ>)4bTwma_P0n1y0K2NR z$(bHG(+Buuyt=)1yqhTk>x8IzL-&KpnT{Gb0tmoF(@vST1!Kq{G9${QC1=9ZFxt>p z)|^2?lV>j_JwTJ*^%45P8QD+NP0l3pR&;eIOwNErlQV$ii{uQlJJTV%j0{+Zs38;c z_tkMi_O{PV-Xu$XY0>3j188+FJ&~CR;0Eiib&Ad*dy&uCWzDqW6Wy zscUY=wJu|t7LaugwK)jJwSlg!m?O-%2I4uK8*DsFI(F-v%fBPs3VhCP$T%_-!G%XM z9rVaNpr`7Aj}0=WTce(aHB9%^{#8rfU!pC1g**n^4Rzlhuuj)keI88cx$4P&6NhSW zYGIBGO8FtSya?f8jkVl58p;s>=DvjK_zJ@uufWzV9qpmh3bQ$#lZ#qk67>PaEaoE} zFSu@cjTscg(x+0vdS5%)nr%v9L}w6?F&OEyTWb(2)?|8V3YoAj>!6+l7@vAM3Y)z5 z_3<8^FLWi)?RG(A2WWiq47<=k9w8)O-z#kXyct}<#YOF=Q zvcR}73hUKM=a!9bzha>vd*^xCJIl)jrn8L};~H~6nS+!q#97*16y%&X)H5P+bjjkREZ1n5UDJwPgK4SSJ}nmS3R3DO<-_6d$-omA zixz`C4We$^-dh8V3XD%Kpbw@& zBMv}FG&BcYTBK+_tlqSNcLpy0orYUns^b|RgT)t+zy!LTu>9;dVPR6_tC~%H&uk!> zWMQ>g06rao32{fJSxC>w=tPKCUYkt$91;QGvDZ)03wvWU!_t6=Z%$hB=ClLI!v=30 zrU?3nfu&7}#lrxmb{6k8iPH9BVd34(U7E-jN|MZ1B~_@g*rZjc*@dCTbp*7t*!8~X zVt|dMy1JvEX6+Q1;EQHsRG~#)LpLZN4;G^^mo&5`aZ+0nX^Gp~ip$yxPWJs8I-_+M zI<$48F`7;!B}Ge2E?Y1g=_(uKI;&UqJA6JHcB0S5xs%Jo2FgpDdtGDEQ0977ZcH{l ze)eDgy8$^Po)|1FKo9a}6<}afK?x!rbZ|aHwxEl=Ok!A(L~;^6>Fg{@lXWEz`#plI0HHUn}j4|n&8AXa= z%wZPVcY)7Cj7Ch0AM_a3wPL?vMva=bXh1TUQU72e8KbRpKufPigJ(U1!*T+_`ve+; z1s3RKi(e|%F_>Qy0P8WCnV(ND<4FJjKmbWZK~$C*bf*I7-}O5PQX?Ov0>L&OS!1Gj)l!cvDH0x+U zG!C9E8J|WIfn>wE_Qc{a;Uix&;)uuMN-6Z>FQ zrk3X=1{;bFM3~deZM4SM^JxMGbsM+P%|$4{sNmTat>~)LJJLq7gy*ft^Ogd1)#pv)Ji|4p0}cYl^}B$N zOxE1OA+wCvyLmVm<$2a;0J)bS0Kxh^I=9FNJY5Q!qMse$%ht-%Rg%Np55^TmvI~j; z4SPshj*zsSm>o`pWo|A)a9@z)aoc(_LO2IaY3F_~@TsGW0wOx*jIdD$_=!$ZJdMiO zm{0tW#56|eujm^vj_#2%#Uk!+s??>6rYj{Xk$2mLnYDO71z5bi-=xG=VZEEYS(E6P zotzn%80podVR8nYWnOh%p^+zNDy%I`&gk_cZ!}jl3}{ZSX}xPbP^Rl*SYWHNrZ72^YVho_2DAk}x+~|Y z9?!U+aeP`FNTRqVrzkc%WSW@r+xd=E3RyQ5EIx&G%N$2_kw(2>WW^7X;fgQwN$2E-sh46OaW?bXL8i1os=35OLuJ2CQ&;fpAEQtIH zbg#} zRq+i-U!Z}D*%a@eqy;ZU6nTaR%rkCG(cFxS1&LIwF-Ml_6GGPuK=I5 zd*Jg6-QoYdx4j2GzvFv;4}5+fH`lHc4HJw`I1X1p+*X{6${n^U|0o)NrlQ}6_Bjf0K6j_EV4gYxLW}PZ7=|6*YJ!^ z%FGgsFWP&Tx0WQz1QxKha6fFbL4o-I08%NyIHLupPy`4C$jY<8QvINM-^r#C)*0J| z5!XOQ0c!kIJO}LtW-LW1pbuI~)4ntCvaQhYQ7mNhKgVWa0fx{_7}jHh&uNI0jg)b; zWFci1R0jBD5o4k12=M6gkBS$-(-$Jf{D3W16J8i<{qjB)>YrJxy`gn3Lxoh^0(){= z;xl1gs(Zb*+(qJ4+M@s}v|On!mK*S~-+&+60GulpB!^~WHj_8!S77m2sCU>zGg&*l zU^Am8>LKc5ANO=1h@Idyag`7q6wrLPztZ z!C7};uQ)g-zvc{KgGH-sW5z?5n#`*K3^j1z(kK_oJcNm)Q<2(Lc7G|0Iy zWNKuSa`~1v%UAE@^36L*f|ezFZb|jgBnAAKdY~Hvf(-(At3+XL#@qv*$tvdxSVJ^Q z+{2br@@==2x)f%-7&3&%VJqO2%Erlz0+u?9UB2V_iCn!!+iDwUV1SoI%SJdP+XHZM zyJW=c61U$iPF+BguNqbWiNP3PPH0|iw1Gv!L0N+(^neBRG`ec)G~c~WAftdYHTkiB zS&{u@1%^-!7M+e8Y^E9a>P;_W-l$aTIINC;Mc~uHAnjj3&kZ1af)?gQbR#i8dCV9d z#noC3dR%UAol(?QHSU_c-r82us zh8?y_p8`&Q^$?(JPpcN=KFm#H08q?LctB#fWB?`GZ{Z_+g`t+Qh;2YydRZDb6&R zqjZj}03uY>fW(hhF7DysRi6LF6`7(yH^zSg_vC(ciFUmr)iOmu=G!uWZWVT5 zgZsu?QK$ig0Kl@MQEhQL4CvV_z}_sLQs82PUG7SXxguGHx#gb|E5M*r!9i^|Ul^zJ zVqBgOF6JgXTBZ&(O`CjHF^7yz7EqTK`vDAZ98$!BshQXBlb{bkh9ck?Or{QuYmG+X z6qYB9p@VDM4Uuulv@i&y145s@Q8u*WF|qT!*?HcAv@0q*eUs;nZh0dm&;#7j=Pi@X z4WGBGid^y?>NOaVErRRNqVuD{K9*Z)0MNV4uK`6e!TiU{GoV_X>#N8W7~?23o0%hA z6#Se3gY9eX7tHW1MV%b%(h6+fntM{}u84R8K>#|x%n)(bCCbXwu#!W~-wk}~2&1Ms z%s1{)bDd+$%do+g88d?drX`T_nYUOA0Aw|?`>@`QBuQ=@B%6RfZ3BRQ3OeX}Dwt|F z47!P>4P-wa!VY{0gANdYoSA2R5rCcAZ^86!46m2TmusF~3dq%ZOVsNPEQ`(#3Y!i( zeKcoa<@VK~o1EzvPnc&YBEk?&k~7&lBdr@#E0Z%TGB$}eE38e;nYAhA8vqvvpsD6f z2asL!rI6KPHOwm2&h>|h(8rtsjPwGyshLWrcr4x0v77=zzeRT7?Ex$;K%P#B{6vpX zWjm&sTee_#Jw>)-#!mr4B&jQ6US2!ZB5W3Of5E2D8+KVk{iBf#f zQcasti|^QNlq7X|C07ZIw=kHw1Y}F(0_0hMLd3*Z13s;c11?yRJ{Xas#7x*Lo5(6X zA>v3S4FGL>fVX`jn@8(6TQ{#&0CMeUAwvN^VaU;Y<$z6VK{}?0hWr`1bPkwUY&t)T zY11p1ZiYsO^DbkWA5dS%v<6XQe9JSQ>6oTtn`%JU+l*@+B^*FD7-3Ai8Q1z0d+IO* zo0xmnDIiy2hL&wEsiHk@r`YHjVGN}3;R|5GgZ}Oq5(Z;*PsON`PBXsEkhA_lL0b9u zJOjHt1N(BC>kzP6bI51W4b=gZ>JlBgj!c1${B04+T>dr=T?5~iQ>_w2e>{p-Ym{}| z)GS7oT8IYxoCTa5)QZvpoa``9RV#qa*Dc8sIat{Px?1||I|g)mvG5Q}^As=-fFI+i zM7~dcVrW@rjvAdoazbST9!g|xK^BUaPT2 zYap3{K`v|mc87HuYafj|MamMyfg&bc*A#Wj{M#yHWrf`8-ZMVG+>QPZdgpuK^EWGrs5djiwp651tq0UB@LEDnhj1i znL{J)nne)|LjJD80wsq5Lk^8aHJDLH_K$}Gifqnl330;~i_Fc+%o?q->x&YNjNvkA zm5>L(#?Gp|g&7B|0}Wlc7hub1loj}-y|;M_e4@SOqqWw@!m!_{NRIE7g~gF&Azh_y zxsFqPjY_vC6qeaFT0~Z-X%C5`CkPA%@HxU}#sU*aH~fY<^8xr|LqN-g%R|+X4VxFx z(?grQ(}FG%{2ChoO%rX;auUs@D@6Ud4*OI{_i>!LD0`$yTcABc%M~#JAho8f8rqi^ zX}cPwbME-nrY$RTB){nx!<+pL#ZDhNtF#lmP#q7DoHfvs%)OV?4dcs=1JR# z$eroc1)O%t8S*9?!wAGd{N#*<_QDLg zqB)aKaWB~9X?tD`;M4cTflpXb2Kej(eA;9bkbDBh$HXE4NE}uq+Iw^4%dFlkT+gBJ z;Gl|YFPoVp_ddzJS4Y=CD2Ukw49E#K7e2Rm*ywrL^tr7cfloCz`u)Hsi}-=I2hqCQ z;<+x-uA0lh{DXbnhIwEON{QT38G;}im>3sd!wKNYj@d*dos^566FGZ>TQ9F!9kjCy1Yow%`(muDE}C)otg2^Jxn z2ww=@s5rETxJ(2_(XMl&C+Ct$Kyf?g(yPMyggKYS@%B7Zmvh*jc^Ktg@ZqQs7g;!wx_~m)6nie3p5F zP6Qs!^P*K+fLIJRVk1<(Wo1D%|B!iD3N!a&bU|^<3&T;+KhqA18h}O}h75=xfn1H3 zImw0YBl+Rw-@BAq2548y!WzoJw7o2$35p>SZ1xmD>lnGG&z~Ql#WfzrVH(ya`8fu_ zxX81nq0A!}n|*!Wl&M+(L_W*@fA-$<&5h$q-#*A0n4ANnBud`3y?g!s|BrjUyKBq# zT9G0-iIa0c4)1-cftdk=MT(+Cf=}Be07Ia=>#kGPRZpEd#qQ9D3}C5yH3svo>LJyQ z%@3Irr1h3pqfw{L8s{l4@@M|D;_zHvRx0s2jAm; zw~MWGg3WBxzFpAXTY1Ie*i4C#BcBnT=n?kvaB1A85gzhzEg;gz@XYf~7hMFaqpo(6 zOfI?2xS)Z@z1vHk4a4sUD}K?w&@9BKF2fAQx?xdm+aylY`BepPv7#G=hN{X zF{_!#-P~!jWWH!mc8Ye<9{b&dPa17D|0Jvup&$x`f^bjsujbgGi8!@O2#ra4F-ayQ zS(s0r$veW%8Lu@O0W6e08{vE~mlVt5lU?GtvpVPiZ~Y%x-tptcIUA#~Jq~S}KYM5I z5D%PoW&u&opS$B}e%No<+CCxAW=Df|?c9aWFrmYW8Azt^^z=3@htSr>u0or}ijZ^S z9UtSAaGWUF36pTSLe9o{R!>leD&veP*pSP2miIJW%CHK76;PZb-@{{+a54?*LF1SS z%(ISd<1M^%M)<5#yyJ0#XfRRHpsht8WCDwoPQG+A|!%hG%^o?&vF z=G#&}$;JJ&uMX^V>#^-W`Nj^Ovhf>g>Bc!LitxNXtWn1Av5)_v@R>#Q%udeZ)jA)% zP{`(Oy% z|5qN$HA~`=+2p}=$w2Yysv5jV8$CP%kPh)Bu{2(yS>Mq#!EEpuo;Od3gNCQi{u7qU zcFJ}Z8?!Tn!2Im2-T87Qq?OO;fFUFn5qAotgfDfc-{&4czGN^)#{(A#1ONsQ^bj(1 z7_vFMRuBPWbSS*j;ycG*>?7!Gd}l|S-?O}$WutV~HgQDQtKl`6L3qTQEJ;k7al8=j zFxa`bFvToBu^aJ{DKjQ2XE~q8(qfrp1CN#(%lWS0moCK-m%U7_MOTr^~e7KvX z?j9GmgZCIdpx8~Pik%YcYmXR~`^2L=VbFDo@Oh5#nVWszF+N@RB#{4RBz$J4?pw*@ z@6E968sYQq;uxu7cjhb9J9d;tyh)N8D&Z%G*ctv=Ml_uES>>hC_@7Wk>X|_T_)Fw@Et}FxE3~fys zq2USvsoF|%T_2n$tdL{r6CMDW1R*ufcJNAL_xR&I+b3kh<`WW|_Us{0o^>mDCyudn zwSwpFn9Z$>+59SP4t7GHKloxLiEE5o0pT~;G^BuN%u?LBLW;o{UTp61i1$D@2(WaJ zX@3sSn6 z^eOG=Q$jgxF)@f&3hnJ4gZQf?&)4Y{E3kyIOjwoK*%@0QR_`)tnn7xT38*5IRT;#{ z9^NZ0^4P{hrAbWD7Ukw1IUy%_3dKB*2lb@IGorEocj8I2i5>#qI$m*4@t}Hoz*5x2 zI08A(CzcJCCT4AAb%ABwv^TuE_sj%!+7k<$xeiOq@e=e{uRH{!F zx8vt)Bn0vumb&lNZ2c&W*H)GZ3+fd0&eJsQSz&yfW`o2=howT%X8ZLGO@KBI;hNaf zz7oQj4(i(p;&lzr>!#-kLH8K3XP0N~?iow>S;pzHJ_kpSkO-Ed4j^QfCTDH&{xV?; z?vQu}BDA>RI7=InMf|Vh1zBf;tj@%0oxyaSNs z4TMkH7(8XGyZAmb$y$z|95ExG!~QqO;9Lf%Uz=6KDAwyWHQgCoHFadeB1v z#WT5EW{D}{C(BHI6Q0$+&8#eum$Ce{&E&9)Ru!9JRY(WV+#cbAg5A)EiT7>B1(yz< zZ|D1z<@S28o973ow$7x|+98t?OdNCygj67GL9()JOT>cz{O*iBA}!MwEmIanME4@k zw;GegS9q3Iuc!+=V-ha@6Ye2Fo>DvulcW@7=%FSOJWo8lk&7djM>{+>cI=D^y$#}o zez!)rne`lAk)_{H_{>3 zvjG7qYS-#v6^!?}uV~Y}U90(0_t2&xqZ4mCiAd^gTAH@4iD&Scw`y5^ZLVGRqT!hm})!@#V_XB$o8!1v!=!Sr-GVLliw!&DAb9!;09u+}lNjq4kLmOOM7u})TVgH$z=X&X!a7tL z(`2XaAQ19goMxjx!g1VTB5;|0)Hv;SfxeMnC&6Y@RKt*(uu3ukkm32Cg*{IJp8dyR zBE5P_|B1Hy`5L10`WkVuX?vMeIwCB}MS}^ES2sa#K;q}WGAw-h$~;yZG9E3=t`IMs z6>Ib>=Lj!0gSeSvQjf-&Hidfa{R8(Vc0rVh{-pO?sNXT_x5w|if~O~6=H3ab`k3(G z`Et%gz%Jn}_B`!+m>Rs(=?5Kq8!3;^pD!?d<7Wv-`21N!AMY0=e15!Tcl*v~?lJpI znz{SPrV;2SS)MXUN6}w%1v*0vO#DHaVi1?$VI3!qr!Nh!GZ<=kVgS}+=};Tdqr-+M zCnwC@vuVaRY?|@xIm zhX`0lcq_Pwb?@Jp9sRde#Lp#SK6R~v09Il!nPRP+Kihr$6qe0-pno3(^BRJ)vkWZh z0|bVbZM^Q<>|9$vCywOymOZD#_x#x!VhhW1iKBMFlHN1#B;tKV1}(-iw-OI?_Zpg; zU$i*}wKLZaEC(d!95dv@hf6yomTaa8n4e!_>FY8vN0(OY4xTp;;Cm0Ta3bqo1bBr| zrcTr^*`k-@14;gi0K$1;)V!cvpZ=W=F1t}~&^bC{fWnOMIx!T_h6a1Xck%0dy0ICz zn;f>{_L67QsR=sni-^ffc!Vs>jpI$y!(#~{(3k2m00^70(79~)H#ZtA1&j3>!DbS~ z!fFuIf16EEcJNBv#cmHS6OqltSXTQR3!e$%n5Ik1Ax+J(k=BDbyXOhdy*x{KiZS9@ zlDvTGc;FA%pEZ;J$;ttmuLPc1w?pT22~PlP0o>*^v)2Yzmo zE$Y16CEe+;9IHzQG=_azN4p)Xcu*+dQRFa~%oJeDX=_u^@>e9n_gZ7Dp9e0JZx zEzkCzkq}&VH;L`a#KX=x9#a==wuX4$n<32-=a%}m!m{Wur|ceS2G0oQ8-kRcrhfb6 zLXRcyjE-4K#gaTcgo?xg%@X4>=9_)cY4+qISrr$lDz8mC$vu{Z;{Fvpai6f$)VEKG z)%uLOLhMs-XMUGm-Q81!C5B`gafxMO47#RP@7eSnLWv-XEc)_7LIg}BfM#OY41_|dMk$RaaHc#*nzeBRw0XjnEFf4d@Jh2yim}9-WuKMG=aBg*S*Ys`i}Iy9k0{Y5PNKk=fxHtHD@f*bulnW%Cp>ca*p`LOzh0l zUeDmoJ&95sCd;Uo_`*^Dxo>}7P;Ndb^|;jj?DBgLk1pz1WUgW&exBA!XxRDNxwE5yzsm|qix{U zt`84azytt6;w+1|@Z{=!o4$(xL0ZNu{SMyqcZdmxlQ}lwaRW(sw)%wUAi2be+bqvO z+BjZw2t1gcWeDZbWGVQEAbfflo+O^6DJE=EZ2I97Wo|E?X7Ti!LrLHItK|hMe=qMwH6P~4Bpqi!I9g{ zEHPqzQe}a5+wGs1h%E;QKnJ*< zpEG&Taq&7cWpP3-Evzo_99*`04-r9m&QBtSOj7qI5wJTggjN*X7Dmu!vbcxP(PQ$s zM_&c-Z+Jq_)dw`&+o*xZO`a4;F3 z!QQ4NXxpj>+o4@M`H9VR2-&pJ9khNC3cTs=GO&p=xshbN zlcdkqsk36>WSesn#)hy&TiD4ut0dN*<86-zYLy8&7nU3RK8%Up6J~E82D`fZ=5GbO zJxfauth{*N#^-RNbNY@=Sjfo}Sv& zlP*pp%(Rd$359lO&k57^{IQ3*pbf$c{s7_gkUm{g!smM`s(mAc9-AT% z@X}5QrvEp#WBi)*<2p;p+$C-3h&pJj$zyE65~$3CCGe7RugGN{-b<_VHb3Wsy&N6A z6dgS_CgJ-2(DvlN_Rg1g^8D~wydJSfd&D!k+}kD2AG?^6_E_?|*Qwy0G-dk;pBcP% z?y=na-X9SEzFbCFVRb9QN|{6-`ueziZvUg_HRcaghL&C8f;O0uZm@H$cgTGA=#3|& zr@L%8L;NiIE;!;5OOg(U3%qQkgF6LIWs`Wg;ZaMPKunt_PN8=MJ(ZcY%<$kQgbm9f zquhB%Bix6-;ZHGM-NUrb&Z&)~$1Df>iJem~*l4B7(y%H*&ua$oJ_^ROCXP2zdTQ13 zcro3b8?!Hnne`bR=^5huWav1cC2;KsfnV1z>jR+}9a3NX+PK)a(zK5V-V!%UQP$#B*kcVML_UL8rnf2vNgr-1rX%p7q zkmXsP4xVq=Hr^(iCv8&8j;%bXf*C@!*d(aqo|am7m!vwsNNu zw`F3Drt2()C6;gv556O~`ZCz>w-6-u2sOb(%gy089^H8&Q@}ZkotRm6S6Y0CcMo1# z(?upeN;ZePxqE`85qDE0&u>)cowCb6-W%XVV1FCict-&n4esIAqyxCft#q zBPy@(iZ`N{lRuwM4q^9Xn{p!k@ce~Ix!W1woJqW|h$R+d$!3wsqq${lla00JmJyR# z_C7`1ImxRFoHbi>HVjFlY z9uYnw#w1k?(JqD8S7nA>(^rTE>51Lb!z39Sk+_(Z^}$~MTHf;Blv`x4(I0$Ms>f_{ z@xKUJ|HmW*!Y2dfA9p(Jn2hk*@n4d>9=oUW%pJUw?&GBtd0(%vw04y(pC?%vfG!#A zB!is^AX}G?5eyCs&^m^xsqU2hqJo|h{>%S5QkhsA+7wq`lq|NLR@`EL>L!Mt4 zqd)hs%p}Xf3yTjJNPl6aWp+Wv{vY6h_8Dm!;oM8g+b|dCA`f@kPujBIm(XPd?!)i|tG< zuza|0Gt+a#q~)38G4TAAR-5NLqKD^~@*7l@=*<39I=HLBjB*IfHm^egJoO%>@Iy&(gL3B#k9?^DrgP` zDVU?%ieG`}Uz@mwZDN_W@#N}M${}^jC05O_a))na`EHte*{jkfT>fV}Hgkr}cuZOMYW;~YHBXLNynfquy}!t2XCbr?d7ryOzVBl< z4`>JHsXOF*3RQBN6$t~0EIg}1{`-xvvu2_*cC`bGJ=G2kEBWcgPT}x%h z>2qTz-mcN6`RW-jJNNt?&*SjmySBT{0GJ~8Dj674DS5*bX1`s#+TRTA+VPWbZI{Xa zXWdC#Pd~JSETKX;=T|Fb`{OFMLD-HQ&y1+=;LqB@bC$N2GlzQb^$us*{Z@k2bkZ)3@6hB%Q&{qp@e`j2?kKb&J_41DSAkMjV_ z_dDHD$$32-JUgJ^VLga_+h(GrdbDBxw{~j(Tw`-ZcCWzi00<*>RtG zhyXf^fIUk&daBTW;xWltL(Qek=s(fE#dsby2oH3=yN=g2<;NuT1~JT^>|L^?p17Ax zK=k^PXccSybm|o zzu8b9FjIdGs&8NTWVO^Op)Iyp;pJ%up?8-!)-HVROZa@B#r#Q~Tf*lE7q^7Z(J0)h z)rU6CLuP7)2h9YXA^OoCQn2hXf8OMvlj6(2d;J|VFNnqS{VyDg{2De9e5}Xj@^OP8$r^JR#O-$MRJ!ZGr@M8`SGmmu_b`0c|KHh7O&=nz% zJVDW2Sa#3ykS1O()r(`65VC2+&M5*YvF`}9=@PFe_iTd-@4VZ=K-FW_xo0cO3QObU zk0n4eV=VviWsIIbRHQVWpn>?vkFJlw^*aRSZ(TfRaKe&5mJ@I8U)lOz-PYMW;)JDM zuRQ1!^cbYDev)Nl%=A~t|74L^U?p4erJ{6jQ;4pwx_0m(3c@F%mB*FbeQX!Ibl{oc zKOjcl4#L^q#fyWzSKh_1`$ufWmovKE6e?qM*zs8Mfg~G7l=}8kB9@p7x?xaF=Y6<% zv1>%SZV=4bw24?&hlEEsddfy9TP$^F3IBRK~GKq*6SE862~; z&{Kk`ZlJ<681%r>BDh&B?fE}j@i#GUV*0ra(kh?F0uh~H)WAA z0#2L&AQs880nZ!urI{g^qwa|~h!0?Tp7Tn#P>?w&WzTm(U?qG5@zk;H*YCcB_sOi_hYCgXE%U_%SYNwyjR z18^V7aP``KNqlT`b9g-GF6ra@z~Y|EZ%hS;aE>Eq+*XQnY#I>z7c&2+#zPs+zpvLe zJ8gsXj$<9kahpbVgK1N&3uhNSQmrPyW7EtJ*LsAXlZVyxlP7aX#%IS0thfz-z#gYB z(qJ6(B71DYd|@`a6ozQ_Su7AK2T-0!m5_zHxI~_Z&w_6J3A2hFIYRq@*%Ilz%%7yK za7}lYF$Wh1qeS;rsFfZ=jx*^|K$mRUE>uwdBGBf>QGsd-BNbeArFo+l=$s?4PZ)b( zYe)=Y^L?Dnq4%zY87H0%h!gMJh2F*2(VV@R!}zjYdc>gijmwqhba#}Jo$aPS9>cgY zZ}U77!)b@ByBP~Ab2Kk{Q4#b-?s3cNr&U*;CF07VuWC^bSZsfg!hhiJ3CrL0I%ODH z?7fh(EysJ|UZpwD%*pogxq7nL?8;^$LutVM-eQur)cVe(#m@$m@7(;|nls?Tj~8vC z%A*Xnh||6g2Zj$flwNj!ATe@Es+WNLkWg5>4@%68YBCB_ndRhnJe$V()XJR&{lv9w z66pRhy*VJWh_cMfQUCyrT@-mC@Xo%g@MKx9{Z7>dm$^6N;i}4O{q9 zKf0v7{^DhJ=DU*$)iBF+%lDF;y_^cHHCW5k^oN^s^0AH-oZk;tRUh2nyJTl((I5vP zBFCA_)v#-+kH7oZh%iFj69h?RoldvwPxiSchpV7GkWb-nbJBaER0%@Mr^RaY%M91} z&G}gKNjg#k+{C{UoAy~SViF*3&0hbUJpmG$XfDBAYjU%!VFGYf51kMZp@gHS{jC*8KMsN!L24I;ALeR zA8*O1ApN6&+Of9Q!5NgpHWXkM-*W$DSflxi@gZ`Hj8xZPJS6QAwBlXw&CjfErfh+y zhh(x%af_Z&M~1RpMm{qy)LbrRqav-hvFOyEz7ZXH5>|O-C|YmTnT49bwtn7GU9=71 zY^9z1bm6~11f1 z9LNGH25}y{+x$E|kF1xTrCHa~ekpPRgw9NC> z5OVKH|Ln&w=_gIO-3}Jg6q$INCEc6~Jx?u})xA7Cb*oQjg=2?WCSUcdo>9pLM0zjt7O5^2v35E~ zb7U@Esf`Nb^Wd2c(YF!LY&l9(`;b-|Q^QBDxkH*01m@XO>8|wB%B4tU$mEP~W0Qc) z&xxVYiBAdV^Jp$BvhFx=K(-M4K})!2X-?&Y=n|O-=Fm;qZx~_NNQRrs!npxpQ~Guw z$de?kB8$p}Jyo-@1-qG>Oadd-&=T0F|2gJqhs}a_gmVWXJXje9X#viEUf&FX5}qq8 zNSm$cSg)*lQ^0N?*^Kfu_@$D?RvmKmZRy#k`gv^&zP*?gPw~p0@({ge;Y-x@KvY@b z(#j*2@Yr2?GbT*J{K_n2`TDS`1t-(9g0M8MFZxKv*L^CFmnebiV%5Bz!u!klb-Ou@ zy%q~d zTP6TooEN2;$_kKrnsNb0I5RZbtLMaM8zp3`7{VgX@+3meXttR@HoaBBDr=3y7LcYg zKi;-@ZPaEy^TAT(+QtyZ3u`e~+5;KQL<;D4s@o^NT$jP?iiy(n2l*xC-Aeh26Eip` zd`BFruQyPYt)CmTeU_kM-I`VL*5elSK)LEX!=WXNFr-b+jrQ#7RhU&ZIjwdiocX9_umz%(bi6y0c$RXu_xk zVFT;~rE(E1Lsh!Bc7{xwqQ-VeM?k=7y~eW&EKh<=`%1P7=n(huiKbxP1W#)JlAm!H z$AnZ`XO(>?+h@`q6F4v8>*f_HzD?-f)fKW2z@EeuMC`4~C_Db*7|v^zVbzk+{_&H6 z*4SE9AjM>}+pgLD4j}@`a{ol413Up)|I2EK;!Xq7zv)}DP8hos$QP$_?e0rt%pJbt zvb3xYFQja=F4p3XpD+wx<>Af4{`Wrh2CR!88n*jA=vS*>WdHy~W^0-GvK!w6lL7PI z$IQ-fUdO5jxysERTi=~jsiFky?{EIp^OaPRwo2*dg@mxC7&5Nc1QXaZ9tG5IKKtyK zRUNK6W#hg}4nd|2#xt0CMZJNk$C_n`ptf;H->t!A25}S&>(f}h7`~SWw=gq^NtoZ(nKQUE>|W9KelC8yLVhoa zbIh;aFb2A&unqO4;+Q@M2%RU zDWtl;WX;NlyPszFKz58k)lDlOiLyR)3tA&X8acD%YND^lHGWk$YJ;wKS$x1*0WWkb zG7vwJGKI!k_RWj$D-}zoGS|IZ?HyWVV`qNBBol_ihQI#y2YI&#ZT1g(Uc$j|TsO)I zVCvqL=jm}br%phM>`~bHl-_A`%bjkp6G(J;P_R#$L^SjSH4#_calixcwfu|%esglD zR5OSZEj^@TetPxEg_rI@JoHijk?&XhOnW6j!p@FUDhys=)EaONm#L!S4}L{HLGS!Z zdwuPpU!Gm}PjfZ|GLUHBFC#@fTnE}4%Tz4*> zSEwhj9rn{Okr^$l$K)74l_v$Nf$xWNvk0J3ImxF74c`xH)xix=7%3O3ND9Gv0bm@9 z+-;!fd*oy|^}e3shMep!mgp^H8yYESB(MOvxxPeiJhXvb5gvGbg_)%WK0}DIoRT7} zziZu1Uy$`W*YKXEnN+w`By`@#L~@WM5?mipea{(K21MBFU)297W&(c<4uj~+(R`6r zyQGgx*0?SMu?M{Zz!JHvRRu17nr5t4JUsOaR_RLt`K1tlKiWF1#E?df7L)KlRQ}`{ zPO1QY|21DHkvSaj^|a}hvQyhX$Pa->B7ky+M_hz*JpKNDxK zF2aw^P8O%_iI?n2c>8b({~Y(op_*1=9~ez+@cVvFz)l#KS;Ry^_2Dral+hs?sH~`P zcfJzk{7l*O_L|XCcEsHRk`oihf(}D%6ZxI83^{WPKt9gl2cIiAieXbGScHm2wY3Ji zc4rvt9&-$^FVl1O)J`Dvsf@d(E)zUj#ed6xUWFHuQpKhx$hhvfBS}-Dh@1WIybQ?^ zP)28gH=i6MLO6U+41*m!|2hkR+SCmd2(`hX6wVS6P>1BCQL6uvYPDE4ou73|1DZey z-NKUjof8-M8wp`P0+a)^T7$%v@6#sT=)RRK__C>Tn~o5Ax0w27ztqMlTfKVD_oNG_ z9B)o$b{&@`Jfy&AqZHiOlH3_V)GZau_U%SIz}Fq8ysw1hH>t}WZ{%r%K`ZxLSJE}V zj&HvKcoU@9UBC5ky{d;#24^1R;HTn)N|OwxeH)x)SNWbAeDE6KB+rtn$~0#m8IQaF zOVI6R8{p14iT^dYVklc+L1Mus7kFKqmhGavdNwgQJ~%3kwM{<6Q6arsswjipngBJOnO;)e9WiPgxhIc&9UWt6=O^6fC z_-2}tAa5jGkw)#kF1TQ`u+-%2Vf;G=*^54t3pX=m7ir+g(|_I#2{O`6UGy!mbU(C;a2;8iBfte`w)Cg&U{kqG`yCI zPENOCv(gCaMC-4V%X~6jE!X19o7<#)4c;q*8F%qB@UPOg`Yy|{upN0E2;nXTj0Exn zg>37uRWQ77v6S;-QtS!z8!=$};|voh<0qr9l+f}$PAMkj)L{6=;Qp2JbMPYLK+xU7 zjOgC^?g7_7iY%U#s zCGy43X68Z8KFFrDdd@cH;I-4X$@nGHZMm6PXg0DK~qdX&u>%8?AREh7u~} zSp3uBFvjD7ewVMJ9b!Gd1shk&y(Y19q&_fb`GTc`%C6kCYca07Pb{5P3tqC}5rn&9b^4Qz>uG>fgNh26`mX#?YpBrvh&U3(}BNC-Jv+oox zTdpHi(7K#rCNC3z{QY(O?8|N0V_FcIal5Zi1C>CZ{E1ZfbziY8-7U@bssOnZ6z2>A z{Rl;J1f}|0e=6&`8NG?Ge z;PDdp0wYf*r#^M5{v`nV>LgBI;YJR6a1r$#9+Vyd$T+0~qXPz9+N9gMBI_Tc@&a3N zeJ>C?sVF0O|vr>Gp3QsJL3mPQIfZcEvL3R&&GsT$fkxz%a0vS_xwGVlS-9f z@##d_vOs36K)$CGCy&sMkzHV>8XIdLuH?m(5+h_H$2zbO>xQlgwwFS(q@+jsDH;qY zIUc>(psnA7I^+a|PTV?|o63yLK_%-6u7j7jz6G2GdOz-Rpd^dNplaK+(+E!rYa%~$ z0p}cj%x_ahzprFC$TVX+&8ZMS%JUOB3{(vpTZ z!OL@E0(;Gl;C{Xg>%uX%qGx?#qJ+_+y*I-$S6f@cQp&y){_tzkISRQv360Ekx^iFo z9h}vy54;}b!bM{t_5WsX`BW*91e=s$F4xA&X6Z=hy2Q1Nz-qS7r=?56v2Yhg)L6@@ z3ycUtZo=o+yJ#%#=W}yA)`-k^@j-Psx%Q%V^3t|F?zBP$TGm9GobNPqgW4Lu=mYQG z27M~q6eBGYyS)5WPK-R*Qka=(yP6mkGefd8je12##F)JYoM|oHnB9m+n>Nh9iYU7~ zt-c_&W}l6vU>KMofF04nh8tTHd+z5lMe>{lF`ArR-v}(ttVql}bIHjK>Z>nbypvv1 z|A{`A#qmQ&@RLxyM4RBR%7hMr1|DM*(#$~8^AobgIp&hrRQ17~E_ zJ@H2tnMR3FSJ`H`7o^#C<`3SCsZcSD^cl^@=D52q&(!(861DPtUS8fmRLiiG<$4J+ zMKpKh_}?$n9I66Enx)rmLeip=zzXy_-X<>u(SFx8uIn*LrbWsn#Yi(YAd1QKts||< zP-Tuc^n8LibF(5NoT+X9X@K?{H@DP4@i5`m{-E5CQDuv5q1#$6C+jF5oH}<80Tc;R zOCSq2(`@>CZ-v-2KnQilDg5Tp+3YUdi0+ztv;Xd4_VtT0WOaC&m^5tkzH>lSnO89m zICl|zcD7eP<|Un=N_;!jgGq&F@M?|mmOt=hUT*Xgc#G!Y@>jJ}iN$gSq;RV%(^%hL zaSLj=G%z*D-qmOoH#B8o!+A6ni~^QlrtO{c-3wZ!Ex$ym2l=tMppHU~{CAqgXrjs} z0}jou3S5?NEnMSJX1^Z?U1hv)A89+snQ0_)X@HwHXnJmT%;1)fX_~^VGhNNug?%cA49(Z>y?t7$iTDXm*L;AR8 zXM%|~4!1q)%a4`0fUD{PS+ducojx*?2?Bx#P|Le=7uN}~42H?%kT~MIOTImo%j4kU zpp}5Tf;;}}Te{neI(SB}q-Y2B7p57niXak~k&?Sm{Z^$$0(6=dndk*a@}tj0eslof zb@$JIm80!xB{^*6m|!0~MfY-yQ!Od?WB{h$gC({@`L*oEpDq^&-#hVD#0GvRY=Tq~ z(JT@M=~FqM>FcgVrgg#~W_rt)vv`+!uiwlDOR2+c^=+e)G_p1%Jy@0;F^<-y_-Pn% zPfQZ{2ZiYz2iA{Win+SSB+KTf-O?^U^E%i>*P%xwhUu&*6BaL}l`>l@;B{fWZ*l*= zsayvX5l1)X(@ckahm9Vhj5VfWr!CGZ#nJlm?)KWwd@}sXXcc8u#Wnfw&Vn9e$-o!-2qMC$Rzn(_{8y> z0^zv*8h#b=@Vb)?nQT|pgwIf@0L;Z?|6R;%)!87MBv&fi_vg{JFL@`8GK<0M)~5)q z)ZiFU(orHwLqNYho_>3AvPZU5PR>=9BwW?!n^q&7Yx>hIvsELT9?j3^s%v3f=g6%LWP`6WY-+kqsucC+ z55N9hP>(S1bM8LHqYmD)quYPaTPp^(#m{lUduc}Tk8#AwN!V5n)btEaq;hM`0=R7v zJvDrkI(o4pB}THG*#^knA!5yP$BUmfl6@kgS#K(ONJ<}j$^K0MR=5xun#qK|q#mBV zLgNjjc*(TmgWmg`f4>k)`KOELl3up%HhABnJ$tLV9eMNDSPCqoGP5Jj=7}6qcF*ce zDhD;b%y`3WRD9(h{uey|^%+vnfgZ9`^v(^F1bP-se)uh2NVQ8KF5tT8AGG-|3jOs7 zw8TbtYk41qZ>9f7AgjV4Wc3vyqv;l8{Y_%=NE40zktz1k5e_@BRklu| z`rou1ON;J4PcL^M0o`OB29*rr3cTswnD}ez*VG3Q#Ef^dbf>ufA653RWhu2?z0>yN zETU~}?7+KZ?x#_;X{~aL4azVQ56pvZ_vj@Y3l;D*qsCFxAw#2ge5Xc)HZF1ZtjSB_ zgPhDt?nLQ7g!X?AtKCJjJsk27olZl0NNn*oE}l`rwt1>XuUh^W8_+u_k7w^`-XFfg zOLLZD6uY=S{jAFVG0nDk>OuJjIpv0xFCu?&;{O2rG8~&+?HRTfz`v;sZp6@HD)!vo zYn~4=xpx*=tiC6~_NT?iRQ$psOR86~acxbrJNY@PV)Ydwl<($*lsW&}k@u zcbuH`Vc@D^U_WH^NmH4AgZFA~VHm_r;Y}M2C-bUDdZf(1nc-hljlGA)i0yUMz{h{n z!(TEgh_2Hmb*lDn@c(a$m}8@PlRiX2m-1gp^!J?z^xl2?t?yd@WbVJz%-?s|cMmcz=`= zX82Fu|3>03)c)sJLiDQu@gc>Wzo+@H7s|nCZAA81$y)yZKrkI$$Aa;i{l7KuKj-^5 z6oQy&35=Ixc|!1Se*CLe7rG8HVwCIum^c|Tnk}#&yfOTL!H2$2{U6r(KM?#6>->jx z{uP1$Xr2FPoqwmj|MQ3SpFgaB!x#TQtaT=Sl1BCX#*dzXkC-9z&Umle+Rg!kce(X> z$sU`E*ny8QkCi&tqCayatC{+0A@QTDFPixFyyo*$y_!uNe1;_^^E781GULZsNWa+f zJ_9X9C?EWU3DgvT&%q1_#Pbu6VZd<~%p9o@11XEju=<=gbYH)ry4P*g%C)5DwOmxx z7M=Sf7-HtfM!wkuoLpl6k0pJe$RDFY&Wu420iWhM5LSIKE%9B&-7qYVIYhG`)< zY_ZOggrf65ML-gS3nurbSTI>h$$oFBrQk*LJmAcIm3dyX`2E$v zG1XW7`#5wsrqo?Zh7hwj^pehus6g|ygH#S)Va-CE00ZPW`C;K;$rOq`}Ja z@q|Zf+&TE$dJ1PE|0@Fv@2OAuCmXS^9tn$PCqNxTT2%x3Xl=Dhd|Pc`g?)7%3snbf z)d%b8weagnG}3>qFInMxe+<&S%E=6whnVf_nLb7ween z*EPERkQ4t42!}sFI3!K_%8+6bQjd#QcQ*8>7Aeee5^!g-yxO8j-I*%Yk*#$`4$)4j zOG>!RuKG@BmcN_sM(9N1Mae0dZ;im}Zl~@D?A@%VY}DN{AwG`rp0#IUk^$Gt-sh(- zb%@(`My9&`W4DBvNnFkqWJCdtmQlh(wt{i)LF>HF{iMqzOuw;Z5-Uw-BuXP{cSXSL zYQsN7?7iQ0E1zw_y6i)Y;Z~U3`LSOLpX3V_Yv`4{R(;30^niQHVRmyV+T<{2ezT+K zt;4JLwPkOwtQF^nUF?8OlaZ>-vek|hBzIk$Ot*7edPmR!tB%7sj)M+^PVzKa1Fxet zRWm5+yey#2u+~NN-T8q{)|bb>q?jyycR_!fKQg9hftFpWn#gYZ6zoEqSZ)9Wb|g~} zq%!2xUcKx+jJ0-b{9NC*zSeN375Id}-s(%YTq z-Wi8lqS*ug&;Blas@5w0zc?Q*|A*T0k0PD7vhlm4)bv4B8w&x0BeHnMwR(l_=TWCU zfPvoqNeLKT{wqn@`P!pttpjMBuzyoPe=laM+#iw$|7lg!I~bmk&C%Uk(gVoO%Bb@9 z;Z&ojt1_r8nc`D?=~HGf-}GJu0-7D0*O8oGtZr$VV?0b!c?D)azP%NUC#-+7Qz&E< zP~+&M%9A6&vb9z6`IjNLVYM-A{QT}bh2a)i$q?9PbA#8|eEWfF3=#G!(KD*O@Q=-0 zXnZ};{Fk-c9;t-B6{$=(g~X_DM({747^h35s;~97WTcFa9lz;ecC*1q1elk zsXd<)mV_=>mFLjUEeA@}9f^$`oRV+K|KVe_HHmN{#8?%s4-4I>>OrWBHVe8Ww5C@I z$mZ1I`f$NBOCmnoaKVbRy`V^x5%dlv30<76Dt(vmFgmA}#6~BDHzSAnRm4nW5ZHkP zrF4BEDJrX9@$zKgNmS0&d4|*7MSb1H^78DGEaSR(kYy&I)S1PP@#Q6u4r|;;CGn4K z&8utuw|8PC?~f}kk5_c@)B(Q%E-n)Zzbbg7*IIQluGoyehBbnbH@fpb{Q9P40?4}| z6QQqe_SV^Mu8%7Nngcmk5w)++NOD-wJ`We2-Q!kxqvdgD)iB=(G|g;ej;S@hI`U$6 z&X*ZjH*(-^JM7(Nt@F4Wl3n`>s&u)4E~S0@U1T!RDM0nh+kMEz{%VdY-ZugSUJiGd zmXr)Zwy;0S?5<|gZYyW7|whD^7oWfpqW4WjKJXaV^`;Ihg25ISSl73IBW!>(6tF+{khN(_s!{ zy#2N&EX}No=$UZSE}gz>|ECwR1)1!!osh0(s=Ka>xq^niBR>=JyLFE1{lW)r7yfXM zZoUzs?&pS|bAtkoW5sg1qOW0=BmXRO?Cr4m=^hciLOpd+!g`A$FjE^ z3mIn;3qCmnvM}4#R>gR%p-g{G*NMPo7X1JeYJBs;q-@hZQ6Zypm4tER7_H2#Q@I(i|#GgB+Mek1(0AS&|PV z%K9K#nYuf&Z|-h@+0|+NbaVE$;V@4SBz0@A;@kp?^O;===Q4TC=w!_X^I@8WI`751svyywpCer z^fU}_L6Ihlcc{*?Lnm*qgv#RPqYq22Z_ zZ&TAX+CZV_p=JpiGiOaZck4L{Oe&>gl0atSiIY(L8e8*3YQ~@Os@hfI8hYoz6^K=??CGD9@+S1Vip&MYpC|L z<~t}uW|x0!atTfX1PIG2IOO%XV@f@YEI2Hz>k&6=nOq#4>`=DebHR1$i1WG~Bm{TK zc18fji$)_Np2iro0>@E{*qLWl-pXV~cSprSh|<;rB_~;5Hjl3Jd0CZ}!JEJg)S7p1 zIcx?XjwKd^FSa*xVykj`OLA776TKbp+=@Dpgrwcon%S@aCnqsW$82bay>SrY zI*yKG8ElW6d*NI-mcUAP(gaAUPpsY3SqMzYF|9ei33^~}bLCu9?9X9;F*hY$X0qs# zH}3z>H6EZGEv>Ek;5I}*g?%+|^T`hcC1j%{JwAn8PlJOuzzBKTp%Lj6F3E?Z&9En^ zYS))M&2mO<#ZCie-#NO`0z;2NnVIN4uL27VtDUQz`LtWc>ONGf?;1egPaXSf9V-ot z&&67M7!Ga-r@rLm?djJhyoUtyzrI`zP3mZ$#qG=X7S3v0EP2=brdGMwwl?qs-9_r} z$`tnz%Y4gJpmmDgkXvabcMN2q61!iT|1i$>01+K4{WE{IY<)o@4LaQdbf)6EIS1@5 z1hSVNnao)e;e@ggj$AD`AnsB{J`fd~O_g{XzK2;;iGQMG=p zK!|Jbn|Ep+4>LCR^eOCi&kC}?*q1sK%NQ|WNvY9pu%-4gq9iD?A68EBx1Zg7+r|Hj zNL1rHp_xtF+08p40>%6!y`y;oLQ zAUPWVeu7DztbzO|ATqw_(& zt+kdIHvY+WAJ;S)&x?~!z8P=Kpp%U@Egyzy@ym#QS*Q5T>=L}h`XN-7t!m3hwomN; zs*qP*GEtq2Pi>1;ept=5-eN_o5&ka2sIq^m+;NxK-e&2?C!x%dS|^F+%HmRJCyeD| zAq&vPm(Q@wMc|6nE9sWww-Ff1X+hwe^84IbEa~2hW`&z%*U$&7FRc2c8@#{HS{G}| zWKQM~i_X>gRXb(3&aOM=ognP*Ino zlow^P?HO)7=}IxG7PtISd8jemy0-Zb?rAyEFqt1>n3RlOB=j=_g7Khtr) zsquF4vCfXUK4u2(Xvq<^jk~4RlKdBw8t9y#d}nqauhGCy|g~2?$@lIyP#duBDR@In1w0OCKLP9coz3S zru@M4qqiX0JkWL@qEOFKF4lGaGN6zMKI!gXfYRR!@INr0cysUo(m6U}g`|;_RAe|6 z}ZZ|(^-)7@!G9m=n>O;;YRci42 z9TxW6erS)M`M&2yWP3QXds#~>vL7m1I)cbu6yguhJ}-F$y2^NcaZB@#nWTr%$u7R` zt4z3UhsSD>ia!P0RdJ?Xu{zDTGQd|kAmOJLg~q8~^SbZp)BVKVfL7B~Q!PI=&~pBB zUSWHKbMYUI=R||@YF*(7n_e51U3^_B7cO~{W zFv9qES+c#sJRsY9x)tMqp57mQPQVXCpQ3W^mzl{KoM7aTB;?5xWFrMvJdcSj3f=6_ z5Ln|fJEQReM&6!twRy!LlQU9jL<;2~n>*zXM`u=t7`!Y3iv8zOdUXK%0n2<(-_ETE zYMBE0a6QF+3)@X+WVhd$PO(13=_uc(xz9^dLIvBk$IkUKz;o7_hNar1tqRdaA-N<| z!=_!DS#!AYu?N1TnxOTrAh9yqap8w*^mLMlfLMuFqjegL%NsClQ}|w#UtG~xHNNty zUsv@?yJw3Uo6mJwJzux}!|_%@j}eu(o0~J_0^;aEa#YkJEUFKxe7>2!QLD>6CuQ9* zE6}$GJ?o&ubRtQb9jxl|7myX`vtjL$TkM%j-y{|27+FaS;Vg=2pDY8tiCDPk`EK&s z`vxXX4z2Vg-RArx+N}GaC|yx*4Q!9I;)c_6@I3b()`E=`LwB)Y#fx_L5D|$WE3V<< z6xTz}nTF>o)FHLds6xRldaE_DAfnX1xBXPC-p-EmRr!{_Lr;3L;w*&MuRJT>)Km*L zp!1kj+^K%sGtVoXrxE?g#kA?|ImADs5+x6=GvA=x%=e!Rq25gUxe_;I4)`ju?d6sv zVHkBm8cGdMk#NFRz?P)4T#qV0RNMCII(pcco$@`=%j98+(ZTI3fBgBu!o*zTo3ni| zO?9y-1y#L<`0B^TnecJ;+>M5}FYjkQwsp$->~oxw(tNq=1?{;og}k`@QScI;Zcs^I zBqvR)h@exsq=$IzyQ^MGDZ_`Dftn{TmljMZV??_g8iPlJTf)MAjztxOw$uQ;EM~aO zp9Tg{E9Kk8G(>tc1f~^k)#-%qb=YU?R;LhPM^kpnET8k9e>@Uii?;}kV!3+R^GSz7 z?<;-1Ha8O|<-jxZ`zQScq{dlnm=p(TuoN7DC=^lI+elmFN58-~^<6PGs``%^>zyW{ z^n3M^J??W`F13=!Ry2!+N{wGi44%xL>S833PQRaY3DeRN3Ts20i9oOB-&?W*WfU)! zCg<{pyvdmHFMK_iy@}_KG^~@K4o24b<6AVUB6dGkKx1RJ_yN5svvE*j<3&@}<`Axh z7ODQ9CfR5y^G{lQO$FICkyNpAYv$G*F|ziKu5sLaK)7nCz{~5#v@ZN!R7QQRHtfJ= zjc+hHS(?)x#5Busu(rv6fJ-4lWqeCMD$*8$A{(gjR?To8m60iQk}5FSALFM5+tc;m z-AMU09`C_kgw*+fVBM@K7BS%Klu_UNHh9)s2Qo#Gmv0Y#sL{*=ciB4q7<Kh{AFIW`qMuc?4~qBTMn7@o$f9N;vuBtzGV&c5w|Y*VU8~E!KK`~KV!aUt4)mL z^TJYi7nCaACTaC;^_owb45}4D znF3QUI7J$$X@c#W%wK@I)^*mUF8WF|Oip1fTHq}^S76opHQrEB%&cvU!&^vv#RS?r zAzmRoc}(%7hr4Kg(12?&!U$=Ls*#oT-4b{c@!og-r6x7G?@p})rGrkyXSU%UFL6;n zywnAik-?-HD?1<75$8NUP`D|2ovvlleU!FNGjr5Yfjejw0#j_X5?;TrJ@!NbA^>=>lb zXRw#XKGkL}u#9D;ekioBEciarr^0!vi%3#>wwABE$@WIX|D{B+8%ilUtX{{W^9QA^ z)9T2_;pL3_{+AuSji~1!TK-^S?C6!k?t6>U{C;>+af58lQU=w7NZWibsFZW!e4X2P z<%Bbhyz3)$IdK>ei?b;1LrZt`W{s0BqRR_|G?KG8$YnH(D1nc>$jrpGV;u z61hBO%8Ibw#Ck0MxPd*BYN0RspxgyI?Xy>RhHT3A9-lVxwW^d%fNvOB!&B-vwLgXh z>n+#JkI9ww+CdCsG5P!B>LLuG0K$lv5% z-BY0fH|PCj0SHeB>T05_=W*9FZZRK{Rg3vUoj@}`L)Iq3_?4Cj>aES$0(vMBHr~Jy zCfN^=60jFqIAjiyeu2Pes%R!4SQ6xuvWJ{M?jtH@aGpoWe>vfquWk{NbV`6uj^{7l zVeJ&#hfz_p{@Q|1zg)?lRXQzMS9{y1oQx;1-P(Z=*~I)x(zB>ZnzqXYMIzS8yj|NH zk+f6hJcqicH}o=aM#3-04NtV*`J&>#zz$jAL(qMteoE4k1;swxz2|4xA%cuxLunU6 z=OVFTp0K+Z>G4(uCdS#`=}Kv+RGo-6mi&`n+`!uB&e3!Jso&`lpV!BA+xWF3*15qM zyMWkf_;aP-FRh;D)0@tNo5aX`Xm_K`D9Xk6;L-z^z`I5UN2%J-VJ`2AAUkuBUmymt zwT@7ic9D$oZ;wje#p`Cgt1t|U>e)%=OB%rA=a*W09&R2d=0auh1<|~+98LA}j$&Ff zUMGQ+zsf{vQz9zbBn;BF_-;4w-l*W%5GkN|8oi=q4C#OjK5JjnKGB%Ty?++6rzzkh% zUq26|k{!V7j7_SFzP!l%`ioi6mXu~Zst)iCV@!O_AsoJ=KyW~K<+XZuVi#G5BLEL| zDs!{gi+pP&tFT~Ewdt)IVQNi-;opLpo@3+I=GxQt$q;iF&^WvCA0Czft>KxXz7%w5zsQ zs3E|{&q%O^!t~r!c?EMAR;5i2* zrkqmUBDZ(Xz>WyKXnPW!LHm^-P>b+ZmN?ofim@*iywV9#?>BM2(W91PE#|7pYt~?X zaenQ&*9~&M?tVmJ=*5}-yxCu(@WCSQu?X)7wf2ekF1GQx-Vx_Nc1=eY;yf;9peiNL9V(f@e9Q`((Cl!b=K5f+lUoB) zm+GiQ{f+_m-0?m z-K3on0|;@JMZe$c@_qv2zf0r#UUHQwjvq=LeZpy!gIDi`P=H-nMU zWoC!(4C*>uYpZiFZ#whkGT&10)rmUveyZ^S6phcmCAH_#z^jF99{W0B`LX@w&1S|t z%0T-~F9Is@h7O$`1)(*ds6h=bMB+OM>t43C4gSzX>=MkR^;=pa6NQPDKThv zO2+<(=Hpy}H1vfav~*6s*u3<_WKSsZRor5&KMx-1pbXv&CH$Px`~T z)TM5#zvwezlkM=U&oGkK^OyvDm_ptO@R~`^&RK9HAF0pGpD*eS@e32qb27dA^h~DB zDyKYtF6U5pU-n3kcSfd8$!jKgN*W4XtZWNxwhy{GY(xr_S6)S?8UpOZ2iz=eMr1Pi zXPLmf$DHb{1C69f%t3BAMW*r6VOhj$8{Y_vKflLJ;*Mlqv(5-td^#tO=T0KqO@Xr~ z(rvBPEUAAd>kpfooldekP~c>hPmCHeB>%z(IlQ|&IgEF*{`8Pdbt6j{53FU9m@Gvs zyi^MYWyEAW{fWJzhYpv|k?|tt^O5NlWd0JU+L>w5c)`kfv)p|TU=fE|=6A1h|Epxx zTX1om*PL=vq@pe^=5lh{Fg&@+-QC?X(qgPk-`(3a+0xU8J_}HfI4r?kG-SH2c1X%N+^_y=)VXqB*{Bem z9_Nxbcv5eE%!}A4y-m-(`08zMq0=}z%h`tKJvhGHYmLCq*hX8k{^7iO+V0gGZ8ubua`x5+-^xh=#J<2`4*8zEZU9&aK2GPMe zVReHL@j}6Z-kL#8yBQSurQ}ksjW?{G zl@L=qb<>NRzU<~`?GbS*Q>Q{Rqo_Ggr7Jm48luFMlpW=USF-rT`a%xOen> z?7Za4M6Tohk4XrI%-v1P!?7OS6$I^R&RhQb?ie^%SF873ZCU|(OseKI6;hPRDt3?B zm^&v&$Ph1J!|7P`*-X7)3|HiFmIXsquh+=-LXoY2>~Mx-8FSJ6Xu7b33#IsPbo4wY zNR?MpUGkn8b@1cP=m-;|FO7_nQdG>Eh566BP-M_!49N6nf{t8vSTjW2J7S;U2ajc8TJBskqUrmp@ZCh%Vlh z3cYWK_+``vN*4>k`sqtl=XZ+c77h##SK0QUyEKlJ`usa9@5t_IkD96o&W{twbf7rZ9s{5Fu8!^{hYL6c$jFj-^+;F zXZzJ9#vCyx$6$6Q$P;a*4k^oOYc61twdey2LnFh_Z&`oa%)h6hwy;eR%pa~2!&Z8a z*t_@J;~2(2OPz{;5IQ_`;BUqLCBn(wL^OA_@?}?cVwt}o`m|8P7+;G@(l@jDhzcG% zME$spjmr8nJO)LYABf=lnxY;$^Eh1d62H$x$Icw9+#HXkP}+8y^55KWb>khdRZFa5 zk|iN8QLJ1Ho=5w^pt2TNRYfI%KSaPN7$hm*{M_F*7<9+6c&pC6Rpox7R<9lcd0WYU z@X3R$rM&w|JM5zr?kTX6;o-$k%t5`# zrX9$QaCDl4jm&jKogjPg0l-T#{xzOG(WDSIL3oEGCSGQ2bW+{z?&WZ=>j*xyUjJ#Y zt8OS!s)KMKf3I7-o>^O%Xqpw8`St_pQsB81!QPKeJ(uY3^|6iaVtjj*1EZUgexc&@ zG#kT@2ab(ub-aBQ$??1h*!w#mnZ!n&EZ*{ zrCz`u5Wtb)Fk4!4k_Hu1_S`~eb*(_Z1HLE2^jl(ON=L&IgauT-hplVm9=o+>AhK2S z0+jWucL{q%`wX9pUheU?%_?FH9y=jrqe}fjRcQ~-xc!Wc{ypx`T5-BwO3jjVPSkC* zt*;F%_tM8n-P)rbe{*(RdLamMoaL$j7q*O)EP}jbH>4wn$movtzAesSKy^Y`xW45N z%HR8X_wbSs3+N09hETon#WO$!i>E|=dxg(@6!L0QFF-E6Q`#Ve_QB(L6=TWLX`QWv z-QP_Z+^~B%B_%DH-iFSJW_J3BYm#z{_KJ6_itaYMzLb&?%k6s2`Ka$%A&y>5)?3piKPpBZN+}vgk4P^fOn#E%7P1OP5wWw-oh{Hu=^IBA*GcZ z8bm;)hVB7GN>V{Oh90`R29S~#fgy)(q*H_;B&54T8l*vKIP>0fe)rz<{sr@$&*yp8 zv-a9+?X5SJGs2l;3VK8KVdF@@t^Wng!BfL7bBNw@>=K4|B)l+T=ROwj>G$yK?I-da zhsA&G6>Z&Xt2?AHtdQOM6#&>Wz*mhmo=_3#hnn?Qk=(87nyb4>VXX#P-McV;`rAyF zPb7|_F|EK*#DfvkV9$yPu#p>75#VeAxM12?A0QgSP@P87uTZ+B_Y&MSQ zUULfNj{x5ADG=<9dK5(xzHO0Aam8g5x*U%h_WS;?g{qg7RNt8QIVBP1Q(sGu0c${* z5F*91tBn5XR63B9TXz2v0o{!q6O_V!OXsOo1SlodMV6M^&9E$!q_vIzGq$FmxfR-% z2T(+QJpH6?$v}L%$VFqPfm}! zOyPdz5-p-t>!aS9@`W_9aIC!|9Op{oO%->##1nzuYFT<|Gj=2=I2_)_`1wP@;9D?z z?R#0t7$X51KH@Kb!J<9-T4^){KIyKjAG0izyZ3}u{ZI{<@^CO&aV z*{1i1V|=NZt2=n_j_I$dtGo0Dl)uCOehv`SVD$U2w#tvmc5+=Mal|bo4ZB6}TPZa* zLCjo=B<55K%YU~+X#WAL6poQP{HKrk9e87r7+H+5{y4|l6k|*;k#%XqS7s=2I!t4tLqw2;D!;@?9H>T{umU(SK zxmL8paoCipS(m1=YqF_4n~4Bk+`1vLlFwoPoCqA&TF~P`tl^Arzs;mR|JKcFrKfIR$1|rJ|2@eY z0}fZ_m8$HA)7hJOa8}lz#(}zn8*4F(8l&=V&cX_*&HF`gF!7asffA?G(u?$ZOce?Ku}` zU&jGtRsZu%=D=WcfN&fq3Ff_~0_Pc+3--KGpJ~A=p-rk>rkG#S11^QR^y=r-r9iuh zyT6-EYHRBc0u5Vt&phdYd#Bk9!Vx%SGKwXMk!0ZrdXzHtYUR`_A0&wQ>Qew7>JLz4 z##z$@C!l}@2a6`VazvNLL7HqPfKfUSNNJmZP!Ka4A>JSacNU3tsp^iUibR+&C^37m z&cD;)OQ_+GRPwa>dC9c(JIWFBtO8S?=a&^7_=UWY?BpyqR0C44wM&bOw08MGtaGDzlL)emsT_BXo<3GTIj|vWTTh(^ zP^k|1K|bd9lhEhMchFZWBed>52G!p=z&z-MUf=6n-9IK?>IjNj9F-8)JH6t5aK?z8 zc$&kDYkqeNxAos|cuSQSyml!{?z|XoIGPYz&@Ld7V2hz4=mzsQ14LL9qZ;xj0&yT8 zaov{kJsU*;Hl5$(ni>cbKAO?d*3WZ1c@SjOoR!$6*^u`JeC^J~KBAHdLAHF8`{op7 zmvNcI127ZL#U-@e=EveXZH6 z`VGKJGk?UJQw4ft6e|(_K|V_~NvnoMr%*4e^diC3B`0)`koHfIPr!^#4e|Mh<4$F` zzcQUWcb=c*9&oCLp<&QGr^r*7dk+;!=NvTFO22mOU8?FS?S?JIUePCG0F;^eJ)O9z zIr~M{_xWP=MBKp5-3J4S(wh@f zD`YQEIg(6k_KoA)dGt%Z@6#aKO$TonfHTD_>}a8z%Obu2YmYnoe414f+Yc>p(}yx9 zn>?Hoo8?vVoqN?&hbpKsCAeP9d}%{sD%GXDhnXva-DrOy@|M%#ny9PSs~QUbD@*km zd93DE{hr9k{N_}3?cDC0_j$uG;s3_!|HI{_M&Nk<488w!hn<{gZtcsL(AyBXx|wf^xUeCGwn5|;pT*6A~+@c^E#+Yjz`Ap{K1(&tcx6Qj@!EH)#%|FJdod*^?M|;3iZ{E!s(&{jP zWFP$PLhEcVG@o#zc*+^-9z0Hvfq2@gmn%YRZ#d_Xzmwg_%i6`czNvf0XhR|iN#X33 zdZoWQA&cDXgWy%aKkr_60z@#xw%3cexRH5?lAJph0n6Z8i}Eap)vz}5>Y;@}3QGw9{Y z>Gbi^c#i)f%82BxC$cfZx{?Txgc2%z;Kn4c>MTO<5JiJ!p5$V;#3@*vuKJ1%Y^t!^ z5j$qyLxSTlAd2vI<11Q!zr(0a=))FN zeG8~824@YL!|Z0{ICx?0&xjZ7R}b$Si}*W0Ch*uug@^!uaj^d=hZ&7KH zeO&$|KArAvi?|V>fj=Xrt{y)ov(>77GU+>x>qniXYUCwcS+5fLftkH3E-K!~0$+cE z?v~e04=5pX{AK3e?-B|<%IEkVE<=QzB|zlPir9j;ed=$|ux;uAl~9KUXSQQh1%6j% z9DkrKh*C@bE3=8T!RyK&!&9l>ju!SG2vF9BJA^qzzqfC+@||I@yp#fcM&}bKe;zL& z2G>3AsWSNVZkNCtO8y#dx|dSmd5wAc?$<1pYpmnCkH>3k3KW^b+ihWnUw7NV<8RvV zo9nuLS1o^xVrIWiD6IvQAs65Q(Nu)3S@n$=`!{1cQyRUubSSde`s#5UfRDllQ9q94 z^Goo^=PwnpTcn}27Dn^#{f0Sdb~XG}E|tU}L2GS4SV)m1=o$}|CLP($T2Hj`7At4) zcz6BN3ijPktUq1=$OgPMT$HTxW?z2Q{M&BkcuT$te-;lWin%C}CLc!UYrPn<>SFw*?w53vL$~ zm1Y(d_;uh+6mMQI}|F<|b>Zcq^1rR9pg&UInF=^U4&ze{ed@-;k50fGe z`vT~XIO4^{BN);_QsI;oVvfnDz9E`9@rwAO`6{%I@TM|Q>F2R>*5BiZfR=c+12O?q zFWIH4gD2rjGijc60|JRn?m<%}tZIf+VTLbCvG@ zs5oa1M*ufG^JN3Q$HnjOTbk4z7G=$8Z3up_@%+}r`w~v$%ls;s*OZoig6(k47urRy zLpfGyn3Dm`j(aK`!R5>!>*+3udTsI}<=q*V-6^Tjaome?ZL_#;(iaZRJAHzUji#m( zy`%TW$JY?zaK~mH7$IPnR}|Q4*OP_{2|SmQCPhu4y#9MaynIOguxSSi*IP2I2J*E!D>UmFWp!*%`5#f{|FG~` zJ}3sVBPA>7pVVZEf_{Zo_ZWs--(gI?DkTL{Tz-&)sbA)8Bg5E3Beok6N~OE-CeU`y zb+Ky&zK5u2#;eluwZ}K@uW;h|)k_JGdlHUgFT?IKS%UG-YU|8WpW7ri5&J~+@`+?O zJIRa46=M3Q ziEbIyae@0yxavQ@fo-rCY<}}g?21PO5w^X8AZc2+L?^M^0L?t9pSPw&wY_II0vI+; z_@uIZPWVd1SZ|lHRvN!H;QV>0Er& z0@!P9v4K_@s5@RAx{AdS*Aq&L#erC_fAG#*-8F(4^j9ZO5twahvH7yGG>&~5u{8L} zHN4?E6pXZs@Cqm1@I~b>P%;JtE%nB3D{wA<`hMLq!sS4@0rlYHA@dxbefk*L*&9kE z7nBoDpECBe`ny4hjJy`wAzBl~RkKGc4*bbd?F^h{QKa*R%C?LPyBqjT>>N|Bw5Q(D zt{>tmb=$v7`bSs9-vc)Ut-r z3NQuXU13FLP8gSO^P7X>ESGAOMd6)hL`@Fj0PVLpBPDZyH|s+iSQ}4`rK>)UJwGrF z3=^^^Dvg>wo;(Zxw~)j!H!ztKGmfITIH4rN69v!(wMFx4cd?ukBYdl!jp~h>URw?n z&My_8f&ItB8n6of5SsT;B(myimE|{uGyfuGiZqbHa&W+JK>3clq zko=s9=M75vskbj<%bO+?`4ouof^##!_PTz+dLoM?2Wls1PAe99B)8@_^bxU8{sChM zReLb}dmTaQSKdPdSHW^?=4PVOFHTy;yAs$Y6mYckxP;=Rl=o)7+Huj1kQ2_}2d}t% zA?~h^XA#Xg@5P2$$$XIAQNdn2KY7VjmB#NxJQ=PxPyW;v|E0a#6*#c7Alw-kUIm)P zF!!daRE%lCATE1?%f_&&_Bn#;gMSe778#;X5G79Og7?q!esD`g^5bMH-Um7y0RFAo zXxfyw{Cu{%e$X=R&F9``z3G#Y!RB0-(xCvuE}cu5Ex%TrH6kANw#(i&&b(-oci2oAIR*imUBJEfl_zJhUyXkxuKVco z4MiBPy8)6|1--D!QcQi01OzLXES1*lN;3=3g=dN;DQ9Na zFkOfnGMzU02j8Q}W-85|psdw|=%TSl$zF4Z(YYhA|0dq;WCu&by_TC!LPuG!RNM9g z3k|~1qqiwW{pdMxQwxI*|BCnd#Xe1eryWrfu`M_+zVM4!qN@?zSW4W@_Vp)0j8+rca&{fHq1Z z+z~>JZoRt1qRy&T==uwEuTezDeP}8W>_L}G>aHbn(+e6IXIpLQojP%N|ISHyBA9vK zSDN;*TM&eN1{%lTeooT4e1SuB@_~)D@Io*m3@mp`T&fx(?@gIvK@IoEzK(q&U5ym` z9d!)=|-O!JTG+2{J%phR6udOu(6 zD82P$RIoG`#@faE{@J-Ek)ga-n^Iosi135}UK2j(#nsQo`XZ2!wDIUC-cl|L{wG1x zxbdnvTg5%ZO!aZ}mxtU>X_&q?>PTz1v=#61co>_DjmL4|{4* zC0#ssY@51^3i}+prcNuI0B~lHu#56)dW1{?RQZhcl#;%^HdDLklM%PAefHQ$I(E9L zj6T8~gz@td#A^j(j9J;YNKNKekcGd>^3(en=$_Pti z9Drl-+{)dtUY-B}x7~?`on6YccChA+KtRS&k`Hi3K)|M5i$pab$jW&{V)PB{D_gqxI)4FCdyJ2_4dzze1a^1z3r_9 zo3C8WKl~597Ta_VE9Vwbq9NP|J);XoJyV2A*Z@U`#N{P<@uWsW3kw^k-hD+u$YZXy z7XaPfSskM0$!#d~jcVUujT_bxK~LGtmRS;*MSU@Xt z-71RS@7tTpxqY;Y)3hVus>m4vAkP6vf_k++%MGZi5Q9DRsB7UGE{K1MeioR6YB@T*d=Yt60SC=+ZDqx|;gQuPl zZk_#BE^UjT_KBUTa*!!I-I*{9WIozWx95Uo)hL-cGHJJx_+Go^k9-9@ui`+czAslc zB6NZB8wBWEk9{q`Gz+E4e?|1J-H4`D z{@!ZPN__h=x76tEYF*E*sO3Z+e9LUbEUcWM>DlG zU&d8SLh8XeF`9*uVBR>$+$tvMVT->VnfV+$B)lv`Y?rxhkS-mt+bSwCvFi#zv?&g- zCjORglvn+u@xW@^(>Zc>DTaC==fYV7v4vC`pPmH@Xi zLpY_&dt_>KZ{Am@In-oX(>aK8E8k--WPx%n5njpl&r`Wf!DHZ|LCp?CT#v}}?&wrp z=NaVd5GR>5eHkc(7pO1Ep<_dc$^AmfVq*8*dZ3XtrlF3)!ftD`wv7~9y_^?r_##U^ zD{}11av!)P>zV_;LJJ@LOlPU!`ZeR15<_*8YGdoFzRSDnwXs8#=;9B5x$w?MyN=o{Zl*f{0||!Y z2fZ!-9)}FZGrPj_>$f%KR}nGd5V4;8rFbCK`w~-wpuZcW?AOr=YEKs9oVPv0U>lF* zHDv0l@9edVIGuD^nhP_cs;4n8F&7F>huMPgtV$g(?5wK_ ze9yiFJtnGF1b!hpI1uVVUZALX?puU}O*hmqMB zwh`FD)e&Z8N#fWsAk>4{=4t>F1S2yAJ;L&^91F+tyuL!p@at{c>IgBX9M2z$ zQamC{&8#-j)c77b(ti!+N()i9e3tZ|p#Bi}{B5T8gVBrElKFWm20GxXDpsQCeMzpY z?2LdRMu4 zISpnIBsNjO{4vHHAuGwl3b4Z?Ze{XnOFYzD^ABpue>a%j4nxVO-OG3r?4(ESr2heN zI7b9+v8*(6MQ)0#K_nueaT-%QR!e}Lm#)lbpz4AwvBJvh{3DDs!hQ0eUmrECW7sQ2 zKRsZGViIxWxOw}e{gpe49KQbq{4m;mbb3<{W%#9(g)j z!~7lJR&pJT8Oj0S6a#L zRZuTk$HPTx`#OmjL=3vbJ_c<7%ZsULvCXfid%{dtEGyRQD>-z9NSwD76q+)EQlsfNEQ(n8Tci zqu1Z?eEc)STjVF7CA0rLnz)jqW)v90H_w>IvY+e+m?jaqB3R^w+8XyayA0ePFCb`}^_nSa~{ZP1L z${tQG#AUSOau`3t7Upm>z>L8bXybM)|L0=3)M7O^BV$OZT|5Ef_~WKz7BA`|OM!Gb z%%=n6DrUy2OT18(Va9GNvYX)OI!yYv{vE>oA>VpEehPV*a_qJ1$h z;>asiOlm=gNjIVU-Nu~o`?c`I8`{61#vMGsKRG>{A2AVe8<@l$LSCS5bfr(A_%w$1 zpp1n@gXKmM=uH|8&xUta-x-1ZjOau}6O&47;#L@o@dzuyCiWL>{z>a7+s|sYBA+)&8!R>us_}}9Cum6baFXyrvDK&!nn&$t(1+N!)As7%xQmCw({5hO%h z3qPP9b5jMoq}{o)nH5qo;K!3+&*6rjE_IjQsK={5($P8!SGwBjI)APio@c%>+T$ZW zsUJDp)3<+DtW0ZWy|Hb)f}5b5BDo@)6@xsN;O+>aKL2q+0EY0h`XKiQZ~yR$eQtG| ziRD7;#8BHTe6&7WBWQS8JExG?7WAa$k?4SbuQT0HK*%iy?D7wTyU47Dn;|zO=Ofd zX7my3f_M_5VpraaMgM(boZ2@F=Fu*iLbp&%y)f+t_U@Gtu()IxSm@-}3%ZIK&dwTs zkpssqBF%wroE3Ji+hpdeM_^vtBo7BBds;iNJeH9RBfnvyfX8ibh5uRn1tL$^MB%uE z3Zoi%j-X1IwI_eQTXs8k0ru6~=-;35((UyNB~CgcKcA7}=}uLIEhCovR34B$mZG0! zaC0JXwUepPVY&RTZJ*v>lKPsp;wn3DNpAxd{D+1|za1eqs-v>q`4AMH4`r5HM9@9E z^%FuXEjKmCia#*rZ=LP>iJ?2DK3AO2{Vq+XyY5`gdveDy(G3I-HtVX<7d~OkBw|@y zsQA5J7X3alSH_c#R|kXij1@M!q+c5HbL4c@o$nCtu!nutG9j^Tt zDyB&swcOO(95ju%zGguh+&SB;r#+)IB_kN~mkQ*Dt zJ{XbFIdy(52n|$YFyXC3S-wX6A}H@t(vxraxd3T}rH_O9PyVwwrQ+ zsvxF>EseE0k(7AsZTnI7n!^avRZ;N1BBJqxRrmuQ7$`}1BB&Wroo7m17ipCXzGa{8XfL6cjs^wohHT1b&w^T>66d@by2@&zbVh*2N>bslctHeH9gL zGT7jAJ?IoQVv*hYl}}joo$~Wy)Q*^b9(aBy%450-XKdBPnNNKDoYM_1O_;E*fkzzM z&9Ny+qqwwGXdwO;1M$2}l<6kTnB9(HjNiU+X6(5M#tSafr&um{yNL| zjgMW+Efjg&|ET}AOQ~eS7btrjY5!A>D3qOXH4M}}HzFI=B{F+z@cd#`easBjN$QkH zYv~&?Mlyrwqjq#$Ag3jL;+gkJX|6m!rtKynagxKrA6mEK96$c#tqv=3b$avV+Z5A= zUS3Dd%5#c0`i!bNm)}LLAsPRVcjf=}HnzL|hm~`%UyYuh;a^oeEjJ65DT*1z)JG7l zkv;thwNw>bFYu^MZvDE%oH5@mJ7B!Uy6i;;DShJW1i}q74GJ@bV2$5rbkFP17gbh= zwc5Y1^%3TpsFn;puRZlv63Q;|`69N}*fYR3Jl{=0soKepP+|w!S)6(7RsSv$SHZFW z|z&gkflyu$#Ll2`L(%aR6E#9baK8=fcmg_Ya@pQ zB5_GI*2o$M`B9U7yZQH^D=fr^wIuCElKStDVkg%=FCGa|#c}ySe+*730r=<>w2ND% zen3LgK4hW3qurXna>Lt|JI!?zBun4-jm(I@SDl4CIF=wskXAv+n@iV3370l-Y1Oa! zH1Ao{U21a$z&|{tq7A}C$ytRi4DOiYx@@&907#>O?zv$O^CzW1!4_TdD;$ke+Nivs z;v8#2p|6cFA#hnKk}MT~toO{;W^*k3jhg_SGNpMM#C^{r^DB0D3f`m`>LQpm}ECiVX*;U zcIorL*XWans0(feddGOiC-#wKVuLJ z{>3L7?YuGpY{PMuR~NcX@$EEKZk+C;3)A+>>!@an>%k<}W$1rnMqhjgmymtD_{mrF zDpbN=cP0Lk6T`L*!qq>xk=G_6bN+XmOyX@^yT3ose*CvVVMnfI<39lrCw}*Z%^1ou) z$VFKv?^2_72>U-u>&U2MCwl@@12Oy`q-=*wR%s89eq9N`)e-ru>!a6WG|pZQ(Kyh5 z)K^p5m(Nf;kv^XUy74^rzlPEfOMgA}`K+|`N?u2F6DtHvK^4iAV}PofARCgDelx3T z;+rh-KH*uBzOCBkqVGFsWJFQLd+QRxU)cv1+=bqr7E@^<3|P)ALw`%cz&(9%U(G)i zBR#WubQ)$;#U5-Ue#|<#5u7xsAZ44_@t5cq$cL%@hc#cvqoH=F+I(hl1H-VvV{_!! zsCV*hJJ)ZCCnZ_p_~^9rf^PS6FEuXPbR~ADM{**53;0 zmYSxGI5>~BL|OEFmIzOLxO%VqPbdJ?Zc5q9{Dp7A4uI?fM+<(T5`7_ygCrI|tbgQC zq+JSp{S4I?TRjV~JvM#V+|AZpT_N3cMeICQe`5t$ICqbsT z{tokyta?0AUN0KkLXai_B&{a!?xspBxL!yzNZ;Ao(Ys5s=7yiDOI;87#^rlV zec4@1YO?x;;>9!Vaa^Mvc&`}-5?Pda9r|B#hn+PF3)0~k{wPrO=ml4Y6{?OUB#i^I5sqgBROkDA-o_}Bx5pXuesPNiJwc@-S ztO!M)eAjrPWn2K+gh+D6{xjM%J%ar|=WHs1x*Vy8qk8DQ77z-A1tCS|SF3AJLen{V zlXsLXX?Gx^5!%#x7m=CM^QVpieVN3A_H@*PueRS}>aPnP)IaOHd|rFcRe1{EG~M`1 zi<56dnDpv4SoNSh-ZiYph z+ZA)i7FGM2tX}nj4Fph4CQ?vtZZ)hrWPP$w0fbN_oOJho09???pFgv2jdvPlotEz= zUpvQHoBZ+5e2xF}(Yh+x^1M80HP*iw8@!Xc7suS-&G&1QwN{^m;o8Nu^}yCoE&pThf;{lQr10m&S+1;UD zbPNUW=pZrmYA7|?9XnkPuK8Te_PF} z`$zlXlZ>9#$Q0P5?(IL(les=|TZMHF0MZWVQ^SyOh^?FKAdRD2Be}giZ>iHRmHwO= za5A8~6qI||Fj(*2ttQ{6u{dxKQx}?iwY^%= zLpJn(0R{9N6HPN)KAq}q1IoHO?k;g4_W&zd zxO&Xz0*)=O>h1A;PDY@Ao(~B5eg9ZamxGvtad}<<-CVJU7e5w#`X2A;wO^P! zy4aPuD^wGuMT3u#e0*<)Rd zMYF$7m>5;R^6#;IdIKI;KDTCTD%;@BzO;nax;-gy3DnD>r(65tMR%sHty|52*lQnF zsI9M+@e72@(j+(ihL@$j$v9V(|G3HPTAySx6YKY2cXZo9>`#n#y+&M1_Nq7t#W@wY zy1u4brfqqvbK?t>JznSr@x{3VhJ3(n=69Lego(~3RP|*};F^pfo@ghaqExI_^kHCQiannj#p}JLT_?RGp66 z*%7sVu=xMyS`gG%zo`F{dC6Rp8XbJ3If>GhI;zFbnfnfhq|VkCW`W?^IBRuJ29$u6 zVw~a6I2oqv>tM>F8I;RMe{}9j>D9d-`5d+v%~+l{TSzTnsLW-j9d#A^Q{%-vDklt;}lCN_eIN$P@HgyT~g~_4>vI)E?*^} z|D^IMg8iBlB;T_MLG57b7LF(UM6ihI$S3Dn#Em8Sm*(J&d`IIOPU4YY$J ze_h4+IHF%|?2~!GmAzq3ke3gqH3BCKb#-SBR~I_RnIz!nVJzYihfPmVmOhI&o5Mgn ztrK<&~DC|2_WQGWj_bpzhUSA{gG2xAujE9r^-Ch>UbKa>G*+SGfY) z@IBRIJ@de2^CVxhlWYG*>Vee^GI_N zIl;BPupuTrV{>6XL}VX;a+b$LQ2*>n`7$PLoYb8nDQ2=w&_R7XI`uGM6~GQlLXc^+ zxjb1suIlk?39s}~e7yYqH?|;A2n1*yVxWw&KN#HarmDq(*S57g0+4ks==|JYss5{; zQ@TrUo(4IRj~C~And3_(y*u_Jx&^=!yf;k$3XM)bQO8fev{E@gGfTRuiQJ=B+Z4*| zzZE1_mF8-{Wjb5V#a^4VhWnBr@)vKLOIb>DjthNk42!LE7>?6ghbABr(%(Wwe|=(1 zVB|R7c@g50**GhEW>1#mLB>xdSp4b#8X=O(U8@CoMq7?oumo>=SyME#-OCQ^!{ zJ-YrWQA@Ui`0275eF*La&I-1;Bu1%hD$1V5g| zR0iE$z!D3k8JnQ}Iy6x-?o5I^<<+Y=~=QJUc_e}bI8>D~KS>2kO&Wtu453{V& zaZUyXK_n!o;&V7LxY&nbl0`%ac;LUK*^L<0`N8PC-Yg@S_J!PAmgc=TEkmZGHq zNX>kX!VTqBMK!RjpH@f)$?$lDVuy!Qjp6>5!W?Uc5>`=MRWuPMD9w8)*4_W=9z_P= zEC`|51o%Dudu+%u+#Wv@gks;;!Z9F(R*j(#<1fnZm=NojQKwk#OL5dtVdYT}rhi%Q zj?#5J)IZSC#baapdu^TPVqBXg7_w7oBpsD4GS=cSS!>W~U+~7y?zS`vTy5#X3R)-onM6|3xJCO)qxJ}JG{V5`7D zvSLG88ejMK5?(D)^sQ%e0{o9K{2@C(j5VtK(O-$!^Q_lTKD|DBiG8AUHvk{yg|_5= zBs}r?%IW&F;5wY^KEC--*AiWQOY&jll zd3d0|Da7}m0Z10R675Fu+${55r&(L3jOyx&KnsO6VxsTO?TKaz5T+u1$tOz73AyOC>NN?czr- z=+@4IF_561WqOHCbcOi+-$)?Nl@?jg&n)p?cGS)x^H;PY@4r91D)?V6n^-~wYG<~8 zA9*GiM0?^BNZ?<@EBsn^SOc{dMCd<(aZrar4ScER5_?>`XY)^f;vnZxwCC;Gh@j%R z6)vE2_wAEn0xo`@krLwtY`mqU7}YWIHGVrV1l=0wOr=3Tt!>zlVzhSotR0Sa(VV1p zU_*klRoBR>H_)P;Lv(p;%E*AUd{(wQw8k@?UN=D)kd5#AW}OcZrN<%iBd1=m>)o51 zPO_7zMWyH$fR1w<&y5}|NHS|?r&P-L$Bk)3v_~-c46sSE$O%ZLWe*cIw7{Dm?8CFvfIPn zf3WcJLiQ@&807?jxJW~SuAi+PXib126cgAy>{5Wb0WzU`3u0_EY54#67a112S? zNn6)LDe2S(13B6r*OG|mzp~8pz&_f6OruQxR3UrJqS#Hw>n{cNpfi3>A|su@TH7HUj{fZ_Rf1e8Qj8wD#@^lJ#h*yq^`fnzcdZyv$1sUP#eUWOf;)` z**EJUswTMg>FVODb~+yY(jBW0m>;CM(V5Ny1cyIn4( zy4xe^*xo8+$ggI8rRQYzSSVrsw>N9Cbs`ko3t`XXkuiz;R`$Yf2^U@EjHB=@wXf(EJX z36otGG+Yh^kjiYAD){Kgs_@!c~-LQ zQ3NrKU8&4&N^%bzKl`DaYt0KPok7mvXS@~S6&Ca6wj)6f;&0188;YGOczu&G9IF-K zZsk~@a;7ZUlQ%7C;;0zaP<2M&F?~g7?%1;c9*TAL2&#RS;H1lF>u7Ok5xYD4u89U& z-x?&nG5Ps-zc8K;ymNfp_^DeyZ!e2#j7j>0V&RKx{o$rK)~um5Z^^HuVMPV{m7tV4 zZz*;h1%fC8H;9)zq*Is9QQVwE5E-EC*z z5gzJo+$|!Yo47#w@GrmfvletOoh~&HPirFVJm}`i4^{jha%n+?>H8X{ZNczrV-g+c zmJFi5(n?`-<(i^NHE0l>?O%>N(6H9lc6l}{CVN$3UPEY_s%ajhAKEgN>LFj5c*KAV zTK8&>AF+*@Nlzh9a2cb?Zzz(io!pP>Wp8Z*TD&$3%;3Cbt6k6fWhfP0GfuTF9~SIC zwBc-wF#GI&siwdB&>6u8k=%8`_3D9ig!b-ymRQ)zZu}1B7p`sK6?&a_Xz(S(6V~?q z%k1$N5H02275wnmVo7Z{wp)J7--vYtUfBLrk)ASG2xsn5 zTjC15)uj_Qa*6Sqa1o7& zK#E=?_CctfTf1m?i9crXWl8lRT%fX?%YQ0aXxQ+paub29>CANZ%~ZI1^n;VKQ}$WNPKd?2L;>={pU_2C}c6-?Va~}?9R?pR zX0-3~WC?K<`|SwM&j45S@KA5>v7~3>E2Kff*g{$qfzmCX<*Qje)eUO`PSNr^&dIem zcIw}cF~cEuh1$O&9R*38Cz2WC*gs*%j{3A*(rr95b8}Ln1p3_RreJZ>?^%3f{kt7% z5oyoIrKeKn%EU!;j}4lvxm{O#Spqz18PYdA{S{5|f*>3_t?^szCC4`3+06?co_K<@ zZUy&yD8}QRY;GyFBCLTc*QL$Bobh%PO(HOHDNqJqm#q!xNYSD+Ym|r>qCSKN3fQ!? zKS%U}wt0`^Sv9ff3J~Q$jrZ!c4`1WI+ieu=vj2yH+)<30%Dy(;08e;U|_qx)aQ_cebf8(PkhD+OzbKTF_&w^!J^4-5r{oUvK zyFZH_$DWE}&*2V}IXLxOXRDWAGWa)m{t;b@F+7`clG9I0e+8iuLouG8eWr17HR2zN zC9e-3dlLhN$Vgp=N2m|q|LB%Z}IrQpWcec&qNMtUT=E7ztUb!>Cr~BqdL6jjj7}NJwu(M713ndI-&+&Ut}Q5&)dj9d)RcB zXzqR(HV?$p2A|>O2+)&BkeJ3cnrheIn#Yim3;hT{x2i}ctMC427Qmj#3g*=(NsN>0 z7YA5Z_WcDua)x!LDwtM7fY@$}EiX)n-_OVYMbCbn(c zHYc{7?AZ2%J9cvOedpYJaDVl)_m9=Rs=D4)c&j==APTUD*?Y_jA>e(s7qy+r&7quc z2B8DyM=#*s7b1h5o1(L$!l_KLfBQ?w7hn!?zd$z{$#GmT*wSKpcKwYM{Ylp;I!Y1q zMxVhF%g;jAqJAshi?A1dKkjAbWx_8=1hTH4ZhRCh>%{eC&f3)-aw8XR9Q`P+9rG1L zsD}dR?yQ#5udAtz8*AF|9M+Z+=V{c!7d|=G>`FGR4~wx_A63~}SsHnR1G#v1n?iO= z`em5Gok2({DO2lj8>z|nWpwUz2T;FP*q=xAHcb%iX@T69-vZdk?r)^#YGcW6FTb!P z!?Z8Lk?tj8fOHAm`r9`u!O-vZVqye+H6HQ6v1UR=$dHGk^dxz5T5}Ejr3*1&G}se- zM4yCLGcm5G5{yYNo1N%7U@6rHvE*XCCBgSm{$|v*DAT(Ql2L{^2&1Ot0ZNT^O-CqHMpIwBt`Rhm|w3FrA z4Xs~Ejr$h*1QRvDX;&nME=UE`N~U$lIzX>9tPAatn$0~L`D!jb?NVQk)5yAn2)6|} z;_tSrDn3kxwziO>Vr?UTCwU}}3OQX!cYt`HbEZ(pE@zgM>~UjV;0c61vCYPI5?oG) zaUTA2uL#DgVCM5!UKOm{5STvw0cMC_qE|Q`4o_P;$Czu9vlRD;iNFp1gN_|?xGVMW z>FjZbabyMzXbD$1(#4=M!qa=)z~_OJBB?;q@F#LM4HIAG$M8|yyG03W$Ze+)n@fB$ zNv;->YGs`{{;abuz^G&(iY*Jk%(B6e1+YA`%QozX;O!cG3t)fQZU4sWO?HKHiWigI zRlDOXh2eIkw=xPlbH%K1jQ^>#lV}Y`W*BU;G%9Sy)J^foo#%x}`zE|V4d)fBDAC$9 z)kO6b@y2TqHaIv0*W+!=BHt6os$Y*Rk@;Meql8D(Yv}_=VWFfd7pR&20gUE}&wu{c zxmo#-)7xX^@mXt-HP!lIpo2I&)rdtuM{C?tTh|vhPk_T}e5^mSlIJ!9$Ev%jwvR@6 zIX zq2Ln1_m2B((9CcY20LKf{|T)Ti^6cZPuT+%^l8x2MP}lyF+1faH)XA%W0wH+P6Rw{ zEq|-`)p|?!JM@eG3q^%o#nlDu(8;AI1spGcyrG%idKcuPm1DIy*{#O+>(d{uEL7bo zEDVj5e;G2Ha0?f^&O)!#{e%;~#tB5655C)Coi?PnsWwB2f>>y+Gj= zM`r<1ezw7|#Ly0F@?=d!87yE8yi*#5GiPLUFZ`eW zMU?a(DP(723;vIpAdL(lURzL4zSFwv2s7ig@?+6(g>tt%dU2esWIY0vGAzqbPj@8T z%z$KYP=RH$p(JWOl$_m?H&7xtJb1aA7!!^ZZh+n5a#d4Yws zfHoz*Bk;R(tX&)q@=#uv%ZvI0W^AR@b9gL!NEaP!R@QYI_#geTeByk~WJt;7(E`Ab z%w|n(y{4uPJ^6`MXE@Bm@J=3$9aggS(eE}WXB{x%AU8pHqG=tZ*!wQ}0v@!ovZ#uGGfo&CpoOigzu0m{D+NYK%2@L55?t-ekX&qg{I`Dee;(G_XN`GHcK@cG zeLioWOAcoe!VjB6^AC#$Ubh7%wzRw3T~dM=tJ>AUDXt`49xhhhKfIEzB|YmdV6>Vj zgMiNs5PFwYoF{QtI1Y6sp5CKkIKLGnt|?gd+E7_R+-60Ic8eyviD! z_3$YE5#H~qi%jQCS-)QMfj5LqRP+BM)=vvs7~RAA<+ffSHQo?{ozv|9p`jLV%+QHy zZuhMK^#E;&T~&AsUgy~d4}-6@Q#L2Sai*xpi66~cN+))JNBC#{vJYTpCe|U zUO$(Il}Ih5yY9MxXZG@jTNWovG2wBa$K;kV0#m)scfTH->uyE!X0)rA+Z{P4v98(# zk6`0Bkn>m30kD(q3iLj9x)Se_=%OzR*D>*V59|7}!R>6_df}Dmu@}bo>Zb^QHUqF$ z{mEtmRabc|T+8NyaTFB_12)RD)b?+EjB?sVarlOr#0^NUyF?x|*GW{Zw9^$eD6NCD zC~6YN-vfPn==jvw^lm(R?Hgi~#94$#k%ZdjAMZI_Hay|ML868>)whWlR+2JOxF`33_XrOG8#6PmVKheLJ)y3|C=m{uuG zc`&ie=5iW$dIA!-S}jeV96#ZTncx-c=}|%C%u&e}CW}T2X)8+@Z7C;t4wO8nr1dax zSrPsU&a#xkL7LAAX9y&!_DyjEO;>4|JD4{j5>N2Oj^=%2l`tQwydAsf-q(mMY< zH=w9>E)Js|u(X}&YmRws54)+^d#C(*!>-#Ghq&;xoy%u;jX4p;; z{D>}PVke;YBtORqgP3Brj#WS6@$lCW=We4LS1X}G@B^T96xYf4-e&riaK%XGoRB~=2Qth>(>uw9bEM1@ zLxj}c_VM0HtgdTRcIApU?s7zYc9ZX<5ZeyW?lJfkK;rh|wqzUr!Fg8R(vdA2IXdHSFze$$yWHLV1eqeg08R}+D=V`>}ML({lw}8Q~n?@W6 zamFHA?P4i%%JTk(YlCg{P~jIcLX5KsO12;In?eK$IgVN>Yl20l5uLNri5qg$mDaC4=|myH zlx-@*pHrcgy_80+s;C)*ac4Ke^hkXuF?0VzZ~vluZ=hdOOS2M3eXY-SDUVB&^riFK zZ~%)EzZR>vAp;jebwANljiA~(dar|(&9X9KpQj-uJPDhYoeU?IiE?j%7P(B#34;fd z%@?u0*#h=T>d=xDkHJEEdB!`o zbBK1KGbDG6R&`$|l+}q|c6zJGhQR}uZU?144_tHH?^Y!17f?M2CC(D5E|SM|l5j@s z$4*n&I(D68D|~gH`zpXad>bVYQG!a-X4k_ug&EY4e>X0{-d|2SzdT9`tWPty}HvZMi(hBWZS+^heB*1#m zFy-|;q-YpbyFajBvfN}?RiXO`xqt{Wxjt*r#zEilI_I{E9}y}{F-ZH~@WKU;Gdd6w zqV5|W2Rx|WZOd7;QxG%BV=vujyAZ*;S1$H;Od)~IDI43c;`)=(6kKRB#x}*{*>~<0 zg9OS~qg8|3iM5zO zCc2rjB97`G{CU6jHQR%~=Jy3mWk}ZQB1x+uZH%YUDB219FPQxa=~MepZQ&|;OmO#% z>crA<3guTlH~(HESoaDE%W;?t8-f3rc<;FGx^lEM2rFLsMOQl;i+fu?9r68jMzr8g zpD7gr2j2xQhY1$bU0%T*41|y%|H_8Etxb|Gh1M+k!{6(CEcD zf%ZtfExel)uYBN`Ky-AoCrV!=}(F;8xJj~&Wl{(kkzpBc;$qzhH0rUPJZ^POg{B>9i|;{;YonsaoDNN>iynEi&A|>E59!V2U!~&3E?M^AHM!eUMyxadYT)c^9lkk6p5n^k6O&*(q;t4WpweoZdt#HCZuQW+uD^t z#o7e`!xUvpe0>Hb*KGv

j6-L3pJZMr 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%0AcZXGQX%Olr5_8+Px;B?Y>Ctid=bX-CfFmTMTf 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 literal 0 HcmV?d00001 diff --git a/microsite/static/img/ccf_vm_blog_img_3.png b/microsite/static/img/ccf_vm_blog_img_3.png new file mode 100644 index 0000000000000000000000000000000000000000..27a6cda90b9dc1c764e9b44180faffc7f3bac7c9 GIT binary patch literal 51741 zcmZs?19YU%)-T*0+x8?gv29~w&BUD8wrxzT2_~A@w(W^Kww;c1^FQZ3_dV-=U$0gD zRM%G5uJsi5{#AsMf)p|W0RjL3K$iY0t^xo+jD6I-;9x$!19d0D004wP7Gh#b(qdv{ zN{)7>7S<*Jz}JZ66j-IiWt^c4a3L2&Fyoh7$`0sw3{UDnXSC#aD7Y`d!hjmOD7t1G z@q!Y%RvAp4Wb(2<8r5Wx2urxe=t_$NfqyjCVUc>)xGp+8%=Uf3TnBt$zo{0di*ca8 zs3~FyO*K29Bv4Mdx`|MLl{O=m5a^zZ@U6NZ8u;t*zq%a{AVL$XM%S z_UQKFAiUlmrhb#kjIEGeanTB? zYvrJWe<}2Cutakm!b$Uu<6^ z35R}}PH%TI%c#-ZSKq-gtv_ejNcz?ZQ;cGtJBqZ^J(ev&>}B5w^u_llw?=}H;^rA= zS5<1k{om+|OFRnuc zSdsuHh>2>btro(v-wXigN&!SlFvr4#cmT}LT-uv(IssId&;evv;sJa9kQgGgXaUyM z1lEAZT(na_n+VSmDoUWGJr)rx!zMd7Dr3;eCe9%=Y7YPphN+v|9kqxIu2%#r9EL`W zAfC|}NmCSQ82>u>CC`8wAuY2*bk#J?A-^;BalpA)&@|pH z%o9|;0V_RhS&wHm{G)*~6E)blfEaA(}6gAN^T)sdj{BUuKy4K&ie%0W;PCdSe95Dl^$G8%fa^re9{ z=+%H_aSeS>2Km*}OW&6&mO7RsPkF3SUE%lw`nHlB2Dzzp$YNv0`uBE5?eCiFnpc|* zL9qPrRRK-?UR(KB5HFJ6@SQjtv0X8wJ&NcB@U2j=qGSQG8q!M&+~VdGpwCE{Md7UC zKEE+plG4OM!t`G_(U{Ol(Kiyler8Azuz-(H)KaAVLQJ9i2{}P9!DK|z*v%11Q*K>S zP*hN=Q_)v8PwAGrGRa(q(uA-u)G^>X_*x{A(u8&(VOypmu{7Q?rIq0>QYQCUCH?Q- zUmZ*MJE%L#yFzvmEyWex42BN1R?(*<=dRmeB>6#6N+rtgre$&!WG(D^y?H`FA5gysD zArYvd1)x(zBYRy#b`X3Japaj08puaXdw2$DUM1!?Az1(Fk! z-DsPrh15jK@Ji^FN7R;!9i{j7AX=bf5xEf&aESCTg|XE*RFKtl3q6$@WL-q%btkmt z|ELwYikua;2DX-YCV19(wmqf6$oFXmRVkM{sO|9RW=|FrPs<+R9_?h5W(a2dn1IY! z%TU#N(lXQ{Y#?YLvBGS?U%6TtSRr27;U(Y&@|L+AJCht@IODfQw0b%7xlrBR-jUyO zo-`cB9Mc`u-8$W!+>+ueA?YB^B9|ha6Z}BN4;=~hLbk*2k>r$&ki?eciZL|OHtM|6 z+sO_|3%N&xMl8d*&y1H(qtv8yp$sZQD0(X*l-HKiPd`eN$UI{|VSl!?HOl_sJN%Lt zQXWz@AQ{CO#g!tNLNtaj)~1F<$5iH`R-sl^5mVu@@LYbeJZ%kO&AIBl{iP*M_tsmSZ3sO>2c3}IxZ)!pW2D51#Lmk z1Yjo;YE-*8h&WqC%_-(7$0;g3Ej@TW7rmWz&h@hPsYe6v7jM_6+q%CRL~6)Zpv6hI zmY_JWp5LP#Q%%Xy6w&R`b0EjTBt+LHd`>4G zuKc0cU$afSGsttEC6I-yqi)OM7Jbl9w;j{ZH-I}3N6G!=R0dDhS(;wjd&KP^^+uoS z@$;Eth8&8VV8RQ-7xF?D>>PqjPZ=`0uQ)n#s)}RMC+Gx-HzANmC`WB`LlVBf`|y