Skip to content

Commit 84d0aa4

Browse files
committed
Adding max age metrics for schemas
1 parent ebd5bb6 commit 84d0aa4

File tree

10 files changed

+67
-34
lines changed

10 files changed

+67
-34
lines changed

.github/workflows/alpha-release.yml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@ name: Release Alpha
22
run-name: Release Alpha ${{ github.actor }} ${{ github.event_name }}
33

44
on:
5-
schedule:
6-
- cron: '0 8 * * 1,3' # Monday, Wednesday 8am
75
workflow_dispatch:
86
inputs:
97
ref:

src/featureFlag/FeatureFlagProvider.ts

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { readFileSync, writeFileSync } from 'fs';
22
import { join } from 'path';
3-
import axios from 'axios';
3+
import { downloadJson } from '../schema/RemoteSchemaHelper';
44
import { LoggerFactory } from '../telemetry/LoggerFactory';
55
import { Measure } from '../telemetry/TelemetryDecorator';
66
import { Closeable } from '../utils/Closeable';
@@ -58,12 +58,9 @@ export class FeatureFlagProvider implements Closeable {
5858

5959
@Measure({ name: 'getFromOnline' })
6060
private async getFromOnline(env: string): Promise<unknown> {
61-
const response = await axios<unknown>({
62-
method: 'get',
63-
url: `https://raw.githubusercontent.com/aws-cloudformation/cloudformation-languageserver/refs/head/main/assets/featureFlag/${env.toLowerCase()}.json`,
64-
});
65-
66-
return response.data;
61+
return await downloadJson(
62+
`https://raw.githubusercontent.com/aws-cloudformation/cloudformation-languageserver/refs/head/main/assets/featureFlag/${env.toLowerCase()}.json`,
63+
);
6764
}
6865

6966
private log() {

src/schema/GetSamSchemaTask.ts

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,23 @@
11
import { DataStore } from '../datastore/DataStore';
22
import { LoggerFactory } from '../telemetry/LoggerFactory';
33
import { Measure } from '../telemetry/TelemetryDecorator';
4-
import { extractErrorMessage } from '../utils/Errors';
5-
import { downloadFile } from './RemoteSchemaHelper';
6-
import { SamSchemas, SamSchemasType } from './SamSchemas';
4+
import { GetSchemaTask } from './GetSchemaTask';
5+
import { downloadJson } from './RemoteSchemaHelper';
6+
import { SamSchemas, SamSchemasType, SamStoreKey } from './SamSchemas';
77
import { SamSchemaTransformer, SamSchema } from './SamSchemaTransformer';
88

99
const logger = LoggerFactory.getLogger('GetSamSchemaTask');
1010

11-
export class GetSamSchemaTask {
11+
export class GetSamSchemaTask extends GetSchemaTask {
1212
private static readonly SAM_SCHEMA_URL =
1313
'https://raw.githubusercontent.com/aws/serverless-application-model/refs/heads/main/schema_source/sam.schema.json';
1414

15-
@Measure({ name: 'getSamSchema' })
16-
async run(dataStore: DataStore): Promise<void> {
15+
@Measure({ name: 'getSchemas' })
16+
override async runImpl(dataStore: DataStore): Promise<void> {
1717
try {
1818
logger.info('Downloading SAM schema');
1919

20-
const schemaBuffer = await downloadFile(GetSamSchemaTask.SAM_SCHEMA_URL);
21-
const samSchema = JSON.parse(schemaBuffer.toString()) as Record<string, unknown>;
20+
const samSchema = await downloadJson<Record<string, unknown>>(GetSamSchemaTask.SAM_SCHEMA_URL);
2221

2322
const resourceSchemas = SamSchemaTransformer.transformSamSchema(samSchema as unknown as SamSchema);
2423

@@ -36,11 +35,11 @@ export class GetSamSchemaTask {
3635
lastModifiedMs: Date.now(),
3736
};
3837

39-
await dataStore.put('sam-schemas', samSchemasData);
38+
await dataStore.put(SamStoreKey, samSchemasData);
4039

4140
logger.info(`Downloaded and stored ${resourceSchemas.size} SAM resource schemas`);
4241
} catch (error) {
43-
logger.error({ error: extractErrorMessage(error) }, 'Failed to download SAM schema');
42+
logger.error(error, 'Failed to download SAM schema');
4443
throw error;
4544
}
4645
}

src/schema/GetSchemaTask.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { PrivateSchemas, PrivateSchemasType } from './PrivateSchemas';
88
import { RegionalSchemas, RegionalSchemasType, SchemaFileType } from './RegionalSchemas';
99
import { cfnResourceSchemaLink, downloadFile, unZipFile } from './RemoteSchemaHelper';
1010

11-
abstract class GetSchemaTask {
11+
export abstract class GetSchemaTask {
1212
protected abstract runImpl(dataStore: DataStore, logger?: Logger): Promise<void>;
1313

1414
async run(dataStore: DataStore, logger?: Logger) {

src/schema/GetSchemaTaskManager.ts

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@ export class GetSchemaTaskManager implements SettingsConfigurable, Closeable {
3636
this.timeout = setTimeout(() => {
3737
// Wait before trying to call CFN APIs so that credentials have time to update
3838
this.runPrivateTask();
39-
void this.runSamTask();
4039
}, TenSeconds);
4140

4241
this.interval = setInterval(() => {
@@ -78,13 +77,11 @@ export class GetSchemaTaskManager implements SettingsConfigurable, Closeable {
7877
.catch(() => {});
7978
}
8079

81-
private async runSamTask(): Promise<void> {
82-
try {
83-
await this.samTask.run(this.schemas.publicSchemas);
84-
this.schemas.invalidateCombinedSchemas();
85-
} catch (error) {
86-
this.log.error({ error }, 'Failed to run SAM schema task');
87-
}
80+
runSamTask() {
81+
this.samTask
82+
.run(this.schemas.samSchemas, this.log)
83+
.then(() => this.schemas.invalidateCombinedSchemas())
84+
.catch(() => {});
8885
}
8986

9087
public currentRegionalTasks() {

src/schema/RemoteSchemaHelper.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,3 +82,12 @@ export async function unZipFile(buffer: Promise<Buffer>): Promise<SchemaFileType
8282
});
8383
});
8484
}
85+
86+
export async function downloadJson<T = unknown>(url: string): Promise<T> {
87+
const response = await axios<T>({
88+
method: 'get',
89+
url: url,
90+
});
91+
92+
return response.data;
93+
}

src/schema/SamSchemas.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ export type SamSchemasType = {
77
lastModifiedMs: number;
88
};
99

10+
export const SamStoreKey = 'SamSchemas';
11+
1012
export class SamSchemas {
1113
static readonly V1 = 'v1';
1214

src/schema/SchemaRetriever.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,13 @@ import { DateTime } from 'luxon';
22
import { SettingsConfigurable, ISettingsSubscriber, SettingsSubscription } from '../settings/ISettingsSubscriber';
33
import { DefaultSettings, ProfileSettings } from '../settings/Settings';
44
import { LoggerFactory } from '../telemetry/LoggerFactory';
5+
import { TelemetryService } from '../telemetry/TelemetryService';
56
import { Closeable } from '../utils/Closeable';
67
import { AwsRegion, getRegion } from '../utils/Region';
78
import { CombinedSchemas } from './CombinedSchemas';
89
import { GetSchemaTaskManager } from './GetSchemaTaskManager';
910
import { RegionalSchemasType } from './RegionalSchemas';
11+
import { SamSchemasType, SamStoreKey } from './SamSchemas';
1012
import { SchemaStore } from './SchemaStore';
1113

1214
const StaleDaysThreshold = 7;
@@ -16,6 +18,7 @@ export class SchemaRetriever implements SettingsConfigurable, Closeable {
1618
private settingsSubscription?: SettingsSubscription;
1719
private settings: ProfileSettings = DefaultSettings.profile;
1820
private readonly log = LoggerFactory.getLogger(SchemaRetriever);
21+
private readonly telemetry = TelemetryService.instance.get('SchemaRetriever');
1922

2023
constructor(
2124
private readonly schemaTaskManager: GetSchemaTaskManager,
@@ -35,6 +38,7 @@ export class SchemaRetriever implements SettingsConfigurable, Closeable {
3538
// Initialize schemas with current region
3639
this.getRegionalSchemasIfMissing([this.settings.region]);
3740
this.getRegionalSchemasIfStale();
41+
this.getSamSchemasIfMissingOrStale();
3842

3943
// Subscribe to profile settings changes
4044
this.settingsSubscription = settingsManager.subscribe('profile', (newProfileSettings) => {
@@ -120,9 +124,36 @@ export class SchemaRetriever implements SettingsConfigurable, Closeable {
120124
const lastModified = DateTime.fromMillis(existingValue.lastModifiedMs);
121125
const isStale = now.diff(lastModified, 'days').days >= StaleDaysThreshold;
122126

127+
const ageMs = now.diff(lastModified).milliseconds;
128+
this.telemetry.histogram('schema.public.maxAge', ageMs, {
129+
unit: 'ms',
130+
});
131+
123132
if (isStale) {
124133
this.schemaTaskManager.addTask(region);
125134
}
126135
}
127136
}
137+
138+
private getSamSchemasIfMissingOrStale() {
139+
const existingValue = this.schemaStore.samSchemas.get<SamSchemasType>(SamStoreKey);
140+
141+
if (existingValue === undefined) {
142+
this.schemaTaskManager.runSamTask();
143+
return;
144+
}
145+
146+
const now = DateTime.now();
147+
const lastModified = DateTime.fromMillis(existingValue.lastModifiedMs);
148+
const isStale = now.diff(lastModified, 'days').days >= StaleDaysThreshold;
149+
150+
const ageMs = now.diff(lastModified).milliseconds;
151+
this.telemetry.histogram('schema.sam.maxAge', ageMs, {
152+
unit: 'ms',
153+
});
154+
155+
if (isStale) {
156+
this.schemaTaskManager.runSamTask();
157+
}
158+
}
128159
}

src/schema/SchemaStore.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { AwsRegion } from '../utils/Region';
33
import { CombinedSchemas } from './CombinedSchemas';
44
import { PrivateSchemasType } from './PrivateSchemas';
55
import { RegionalSchemasType } from './RegionalSchemas';
6-
import { SamSchemasType } from './SamSchemas';
6+
import { SamSchemasType, SamStoreKey } from './SamSchemas';
77

88
export class SchemaStore {
99
public readonly publicSchemas = this.dataStoreFactory.get('public_schemas', Persistence.local);
@@ -20,7 +20,7 @@ export class SchemaStore {
2020
if (!cached) {
2121
const regionalSchemas = this.publicSchemas.get<RegionalSchemasType>(region);
2222
const privateSchemas = this.privateSchemas.get<PrivateSchemasType>(profile);
23-
const samSchemas = this.samSchemas.get<SamSchemasType>('sam-schemas');
23+
const samSchemas = this.samSchemas.get<SamSchemasType>(SamStoreKey);
2424

2525
cached = CombinedSchemas.from(regionalSchemas, privateSchemas, samSchemas);
2626
void this.combinedSchemas.put(cacheKey, cached);

src/services/AwsClient.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ type IamClientConfig = {
99
region: string;
1010
credentials: IamCredentials;
1111
customUserAgent: string;
12-
endpoint?: string;
1312
};
1413

1514
export class AwsClient {
@@ -18,9 +17,11 @@ export class AwsClient {
1817
private readonly cloudformationEndpoint?: string,
1918
) {}
2019

21-
// By default, clients will retry on throttling exceptions 3 times
2220
public getCloudFormationClient() {
23-
return new CloudFormationClient(this.iamClientConfig());
21+
return new CloudFormationClient({
22+
...this.iamClientConfig(),
23+
endpoint: this.cloudformationEndpoint,
24+
});
2425
}
2526

2627
public getCloudControlClient() {
@@ -38,7 +39,6 @@ export class AwsClient {
3839
region: credential.region,
3940
credentials: credential,
4041
customUserAgent: `${ExtensionId}/${ExtensionVersion}`,
41-
endpoint: this.cloudformationEndpoint,
4242
};
4343
} catch {
4444
throw new Error('AWS credentials not configured. Authentication required for online features.');

0 commit comments

Comments
 (0)