1- using Prometheus ;
21using System . Diagnostics ;
3- using Npgsql ;
4- using cs_app ;
52using System . Text . Json . Serialization ;
3+ using cs_app_aot ;
4+ using Npgsql ;
5+ using NpgsqlTypes ;
6+ using Prometheus ;
7+ using Metrics = Prometheus . Metrics ;
68
7- // Initialize the Web App
89var builder = WebApplication . CreateSlimBuilder ( args ) ;
910
10- // Configure JSON source generatino for AOT support
11- builder . Services . ConfigureHttpJsonOptions ( options =>
11+ // hard-off logging for benchmarks (no sinks)
12+ builder . Logging . ClearProviders ( ) ;
13+ builder . Logging . SetMinimumLevel ( LogLevel . None ) ;
14+
15+ // JSON source-gen for AOT; harmless on JIT
16+ builder . Services . ConfigureHttpJsonOptions ( o =>
1217{
13- options . SerializerOptions . TypeInfoResolverChain . Insert ( 0 , AppJsonSerializerContext . Default ) ;
18+ o . SerializerOptions . TypeInfoResolverChain . Insert ( 0 , AppJsonSerializerContext . Default ) ;
1419} ) ;
1520
16- // Load configuration
17- var dbOptions = new DbOptions ( ) ;
18- builder . Configuration . GetSection ( DbOptions . PATH ) . Bind ( dbOptions ) ;
19-
20- var s3Options = new S3Options ( ) ;
21- builder . Configuration . GetSection ( S3Options . PATH ) . Bind ( s3Options ) ;
22-
23- // Establish S3 session.
24- using var amazonS3 = new AmazonS3Uploader ( s3Options . User , s3Options . Secret , s3Options . Endpoint ) ;
25-
26- // Create Postgre connection string
27- var connString = $ "Host={ dbOptions . Host } ;Username={ dbOptions . User } ;Password={ dbOptions . Password } ;Database={ dbOptions . Database } ";
21+ // config
22+ var cfg = new AppConfig ( builder . Configuration ) ;
23+ builder . Services . AddSingleton ( cfg ) ;
2824
29- Console . WriteLine ( connString ) ;
25+ // AWS S3
26+ builder . Services . AddSingleton ( new AmazonS3Uploader ( cfg . User , cfg . Secret , cfg . S3Endpoint , cfg . Region ) ) ;
3027
31- // Establish Postgres connection
32- await using var dataSource = new NpgsqlSlimDataSourceBuilder ( connString ) . Build ( ) ;
33-
34- // Counter variable is used to increment image id
35- var counter = 0 ;
28+ // Npgsql (AOT/trimming friendly)
29+ var csb = new NpgsqlConnectionStringBuilder
30+ {
31+ Host = cfg . DbHost ,
32+ Username = cfg . DbUser ,
33+ Password = cfg . DbPassword ,
34+ Database = cfg . DbDatabase ,
35+ Pooling = true ,
36+ MaxPoolSize = 256 ,
37+ MinPoolSize = 16 ,
38+ NoResetOnClose = true ,
39+ AutoPrepareMinUsages = 2 ,
40+ MaxAutoPrepare = 32 ,
41+ Multiplexing = true
42+ } ;
43+ builder . Services . AddSingleton ( _ => new NpgsqlSlimDataSourceBuilder ( csb . ConnectionString ) . Build ( ) ) ;
3644
3745var app = builder . Build ( ) ;
3846
39- // Create Summary Prometheus metric to measure latency of the requests.
40- var summary = Metrics . CreateSummary ( "myapp_request_duration_seconds" , "Duration of the request." , new SummaryConfiguration
41- {
42- LabelNames = [ "op" ] ,
43- Objectives = [ new QuantileEpsilonPair ( 0.9 , 0.01 ) , new QuantileEpsilonPair ( 0.99 , 0.001 ) ]
44- } ) ;
47+ // Prometheus Summary (prebind labels to skip tiny allocs)
48+ var summary = Metrics . CreateSummary ( "myapp_request_duration_seconds" , "Duration of the request." ,
49+ new SummaryConfiguration
50+ {
51+ LabelNames = [ "op" ] ,
52+ Objectives = [ new QuantileEpsilonPair ( 0.9 , 0.01 ) , new QuantileEpsilonPair ( 0.99 , 0.001 ) ]
53+ } ) ;
54+ var s3Dur = summary . WithLabels ( "s3" ) ;
55+ var dbDur = summary . WithLabels ( "db" ) ;
4556
46- // Enable the /metrics page to export Prometheus metrics.
57+ // endpoints
4758app . MapMetrics ( ) ;
59+ app . MapGet ( "/healthz" , ( ) => Results . Ok ( ) ) ;
60+ app . MapGet ( "/api/devices" , ( ) => Results . Ok ( StaticData . Devices ) ) ;
4861
49- // Create endpoint that returns the status of the application.
50- // Placeholder for the health check
51- app . MapGet ( "/healthz" , ( ) => Results . Ok ( "OK" ) ) ;
62+ app . MapGet ( "/api/images" ,
63+ async ( HttpContext http ,
64+ AmazonS3Uploader s3 ,
65+ NpgsqlDataSource dataSource ) =>
66+ {
67+ var id = Interlocked . Increment ( ref StaticData . Counter ) - 1 ;
68+ var image = new Image ( $ "cs-thumbnail-{ id } .png") ;
69+
70+ // S3
71+ var t0 = Stopwatch . GetTimestamp ( ) ;
72+ await s3 . Upload ( cfg . S3Bucket , image . ObjKey , cfg . S3ImgPath , http . RequestAborted ) ;
73+ s3Dur . Observe ( Stopwatch . GetElapsedTime ( t0 ) . TotalSeconds ) ;
74+
75+ // DB
76+ var t1 = Stopwatch . GetTimestamp ( ) ;
77+ await using ( var cmd = dataSource . CreateCommand ( StaticData . ImageInsertSql ) )
78+ {
79+ cmd . Parameters . Add ( new NpgsqlParameter < Guid > { NpgsqlDbType = NpgsqlDbType . Uuid , Value = image . ImageUuid } ) ;
80+ cmd . Parameters . Add ( new NpgsqlParameter < string > { NpgsqlDbType = NpgsqlDbType . Text , Value = image . ObjKey } ) ;
81+ cmd . Parameters . Add ( new NpgsqlParameter < DateTime >
82+ { NpgsqlDbType = NpgsqlDbType . TimestampTz , Value = image . CreatedAt } ) ;
83+ await cmd . ExecuteNonQueryAsync ( http . RequestAborted ) ;
84+ }
85+
86+ dbDur . Observe ( Stopwatch . GetElapsedTime ( t1 ) . TotalSeconds ) ;
87+ return Results . Ok ( ) ;
88+ } ) ;
5289
53- // Create endpoint that returns a list of connected devices.
54- app . MapGet ( "/api/devices" , ( ) =>
55- {
56- Device [ ] devices = [
57- new ( "b0e42fe7-31a5-4894-a441-007e5256afea" , "5F-33-CC-1F-43-82" , "2.1.6" ) ,
58- new ( "0c3242f5-ae1f-4e0c-a31b-5ec93825b3e7" , "EF-2B-C4-F5-D6-34" , "2.1.5" ) ,
59- new ( "b16d0b53-14f1-4c11-8e29-b9fcef167c26" , "62-46-13-B7-B3-A1" , "3.0.0" ) ,
60- new ( "51bb1937-e005-4327-a3bd-9f32dcf00db8" , "96-A8-DE-5B-77-14" , "1.0.1" ) ,
61- new ( "e0a1d085-dce5-48db-a794-35640113fa67" , "7E-3B-62-A6-09-12" , "3.5.6" )
62- ] ;
63-
64- return Results . Ok ( devices ) ;
65- } ) ;
90+ app . Run ( ) ;
6691
67- // Create endpoint that uoloades image to S3 and writes metadate to Postgres
68- app . MapGet ( "/api/images" , async ( ) =>
69- {
70- // Generate a new image.
71- var image = new Image ( $ "cs-thumbnail-{ counter } .png") ;
92+ // ---- app types ----
93+ [ JsonSerializable ( typeof ( Device [ ] ) ) ]
94+ internal partial class AppJsonSerializerContext : JsonSerializerContext ;
7295
73- // Get the current time to record the duration of the S3 request.
74- var s3StartTime = Stopwatch . GetTimestamp ( ) ;
96+ public sealed class AppConfig ( IConfiguration config )
97+ {
98+ public string ? DbDatabase = config . GetValue < string > ( "Db:database" ) ;
99+ public string ? DbHost = config . GetValue < string > ( "Db:host" ) ;
100+ public string ? DbPassword = config . GetValue < string > ( "Db:password" ) ;
101+ public string ? DbUser = config . GetValue < string > ( "Db:user" ) ;
75102
76- // Upload the image to S3.
77- await amazonS3 . Upload ( s3Options . Bucket , image . ObjKey , s3Options . ImgPath ) ;
103+ public string ? Region = config . GetValue < string > ( "S3:region" ) ;
104+ public string ? S3Bucket = config . GetValue < string > ( "S3:bucket" ) ;
105+ public string ? S3Endpoint = config . GetValue < string > ( "S3:endpoint" ) ;
106+ public string ? S3ImgPath = config . GetValue < string > ( "S3:imgPath" ) ;
107+ public string ? Secret = config . GetValue < string > ( "S3:secret" ) ;
108+ public string ? User = config . GetValue < string > ( "S3:user" ) ;
109+ }
78110
79- // Record the duration of the request to S3.
80- summary . WithLabels ( [ "s3" ] ) . Observe ( Stopwatch . GetElapsedTime ( s3StartTime ) . TotalSeconds ) ;
81111
82- // Get the current time to record the duration of the Database request.
83- var dbStartTime = Stopwatch . GetTimestamp ( ) ;
112+ public sealed class Device
113+ {
114+ public required string Uuid { get ; init ; }
115+ public required string Mac { get ; init ; }
116+ public required string Firmware { get ; init ; }
117+ }
84118
85- // Prepare the database query to insert a record.
86- const string sqlQuery = "INSERT INTO cs_image VALUES ($1, $2, $3)" ;
119+ public readonly struct Image
120+ {
121+ public string ObjKey { get ; }
122+ public Guid ImageUuid { get ; }
123+ public DateTime CreatedAt { get ; }
87124
88- // Execute the query to create a new image record.
89- await using ( var cmd = dataSource . CreateCommand ( sqlQuery ) )
125+ public Image ( string key )
90126 {
91- cmd . Parameters . AddWithValue ( image . ImageUuid ) ;
92- cmd . Parameters . AddWithValue ( image . ObjKey ) ;
93- cmd . Parameters . AddWithValue ( image . CreatedAt ) ;
94- await cmd . ExecuteNonQueryAsync ( ) ;
127+ ObjKey = key ;
128+ ImageUuid = Guid . NewGuid ( ) ;
129+ CreatedAt = DateTime . UtcNow ;
95130 }
96-
97- // Record the duration of the insert query.
98- summary . WithLabels ( [ "db" ] ) . Observe ( Stopwatch . GetElapsedTime ( dbStartTime ) . TotalSeconds ) ;
99-
100- // Increment the counter.
101- counter ++ ;
102-
103- return Results . Ok ( "Saved!" ) ;
104- } ) ;
105-
106- app . Run ( ) ;
107-
108- [ JsonSerializable ( typeof ( Device [ ] ) ) ]
109- internal partial class AppJsonSerializerContext : JsonSerializerContext ;
131+ }
0 commit comments