5
5
#include <stdio.h>
6
6
#include <stdlib.h>
7
7
#include <string.h>
8
+ #include <sys/stat.h>
8
9
#include <unistd.h>
9
10
10
11
#include <curl/curl.h>
16
17
"%s/emissions/forecasts/current?location=%s&dataEndAt=%s&windowSize=%d"
17
18
#define URL_SIZE 256
18
19
20
+ #define ISO8601_CLI_FORMAT "%4d-%02d-%02dT%02d:%02d:%02dZ"
21
+ #define ISO8601_CLI_SIZE 22
22
+ #define CLI_FORMAT "%s emissions-forecasts -l %s -e %s -w %d"
23
+ #define CLI_SIZE 256
24
+
19
25
typedef struct Params_t {
26
+ bool cli ;
20
27
char * url ;
21
28
char * location ;
22
29
int window ;
@@ -74,6 +81,7 @@ void home_path(char *file, char *path) {
74
81
75
82
void default_args (params_t * params ) {
76
83
// copy strings so they can be freed if overridden by config files
84
+ params -> cli = false;
77
85
params -> url = strdup ("https://carbon-aware-api.azurewebsites.net" );
78
86
params -> location = strdup ("eastus" );
79
87
params -> window = 30 ;
@@ -179,6 +187,14 @@ bool parse_args(int argc, char *argv[], params_t *params) {
179
187
}
180
188
i ++ ;
181
189
190
+ // check to see if URL is path to carbon aware cli executable
191
+ if (params -> url [0 ] != 'h' ) {
192
+ struct stat sb ;
193
+ if (stat (params -> url , & sb ) == 0 && sb .st_mode & S_IXUSR ) {
194
+ params -> cli = true;
195
+ }
196
+ }
197
+
182
198
// anything else is the optional command and its params
183
199
params -> command = i ;
184
200
return true;
@@ -199,31 +215,32 @@ the program will just block until the best time.\n\n",
199
215
-u <api url> url prefix of Carbon Aware API server to consult\n" );
200
216
}
201
217
202
- void format_url (params_t * params , char * url ) {
218
+ void format_params (params_t * params , size_t iso8601_size , char * iso8601_format ,
219
+ size_t len , char * format , char * param_str ) {
203
220
time_t now ;
204
221
struct tm * time_tm ;
205
- char end [ISO8601_URL_SIZE ];
222
+ char end [iso8601_size ];
206
223
int size ;
207
224
208
225
time (& now );
209
226
// add time this way isn't necessarily portable!
210
227
now = now + params -> hours * 60 * 60 ;
211
228
time_tm = gmtime (& now );
212
- size = snprintf (end , ISO8601_URL_SIZE , ISO8601_URL_FORMAT ,
213
- time_tm -> tm_year + 1900 , // years from 1900
214
- time_tm -> tm_mon + 1 , // 0-based
215
- time_tm -> tm_mday , // 1-based
216
- time_tm -> tm_hour , time_tm -> tm_min , 0 );
229
+ size = snprintf (end , iso8601_size , iso8601_format ,
230
+ time_tm -> tm_year + 1900 , // years from 1900
231
+ time_tm -> tm_mon + 1 , // 0-based
232
+ time_tm -> tm_mday , // 1-based
233
+ time_tm -> tm_hour , time_tm -> tm_min , 0 );
217
234
218
- if (size > ISO8601_URL_SIZE ) {
235
+ if (size > iso8601_size ) {
219
236
// date string was truncated which should never happen
220
237
fprintf (stderr , "Warning: end date corrupted (iso8601 date truncated)\n" );
221
238
}
222
239
223
240
printf ("Requesting %d min duration window before %02d:%02d UTC in %s\n" ,
224
241
params -> window , time_tm -> tm_hour , time_tm -> tm_min , params -> location );
225
242
226
- snprintf (url , URL_SIZE , URL_FORMAT , params -> url , params -> location , end ,
243
+ snprintf (param_str , len , format , params -> url , params -> location , end ,
227
244
params -> window );
228
245
}
229
246
@@ -240,7 +257,7 @@ static size_t write_response(void *contents, size_t size, size_t nmemb,
240
257
char * ptr = realloc (mem -> text , mem -> size + realsize + 1 );
241
258
if (!ptr ) {
242
259
// out of memory!
243
- printf ( "not enough memory (realloc returned NULL)\n" );
260
+ fprintf ( stderr , "not enough memory (realloc returned NULL)\n" );
244
261
return 0 ;
245
262
}
246
263
@@ -252,7 +269,8 @@ static size_t write_response(void *contents, size_t size, size_t nmemb,
252
269
return realsize ;
253
270
}
254
271
255
- void call_api (char * url , void (* extra_data )(response_t * , void * ), void * data ) {
272
+ void call_api (char * url , void (* extract_data )(response_t * , void * ),
273
+ void * data ) {
256
274
CURL * curl ;
257
275
CURLcode res ;
258
276
response_t response ;
@@ -273,7 +291,7 @@ void call_api(char *url, void (*extra_data)(response_t *, void *), void *data) {
273
291
fprintf (stderr , "curl_easy_perform() failed: %s\n" ,
274
292
curl_easy_strerror (res ));
275
293
} else {
276
- extra_data (& response , data );
294
+ extract_data (& response , data );
277
295
}
278
296
curl_easy_cleanup (curl );
279
297
}
@@ -283,12 +301,43 @@ void call_api(char *url, void (*extra_data)(response_t *, void *), void *data) {
283
301
curl_global_cleanup ();
284
302
}
285
303
304
+ void call_cli (char * cmd , void (* extract_data )(response_t * , void * ),
305
+ void * data ) {
306
+ const size_t BUFSIZE = 255 ;
307
+ response_t response ;
308
+
309
+ response .text = malloc (1 ); // will be grown as needed by the realloc below
310
+ response .size = 0 ; // no data at this point
311
+
312
+ char buf [BUFSIZE ];
313
+ FILE * fp ;
314
+
315
+ if ((fp = popen (cmd , "r" )) == NULL ) {
316
+ fprintf (stderr , "Error opening pipe!\n" );
317
+ return ;
318
+ }
319
+
320
+ while (fgets (buf , BUFSIZE , fp ) != NULL ) {
321
+ size_t read = strnlen (buf , BUFSIZE );
322
+ write_response (buf , sizeof (char ), read , & response );
323
+ }
324
+
325
+ if (pclose (fp )) {
326
+ fprintf (stderr , "Command not found or exited with error status\n" );
327
+ return ;
328
+ }
329
+
330
+ extract_data (& response , data );
331
+
332
+ free (response .text );
333
+ }
334
+
286
335
void parse_response (response_t * response , void * wait_seconds ) {
287
336
size_t i ;
288
337
json_t * root ;
289
338
json_error_t error ;
290
339
291
- printf ("Carbon Aware API returned %lu bytes\n" ,
340
+ printf ("Carbon Aware SDK returned %lu bytes\n" ,
292
341
(unsigned long )response -> size );
293
342
root = json_loads (response -> text , 0 , & error );
294
343
@@ -317,9 +366,13 @@ void parse_response(response_t *response, void *wait_seconds) {
317
366
318
367
optimals = json_object_get (data , "optimalDataPoints" );
319
368
if (!json_is_array (optimals )) {
320
- fprintf (stderr , "error: optimalDataPoints is not an array\n" );
321
- json_decref (root );
322
- return ;
369
+ // might be cli and in capitals
370
+ optimals = json_object_get (data , "OptimalDataPoints" );
371
+ if (!json_is_array (optimals )) {
372
+ fprintf (stderr , "error: optimalDataPoints is not an array\n" );
373
+ json_decref (root );
374
+ return ;
375
+ }
323
376
}
324
377
325
378
// just look at the first for now
@@ -335,12 +388,16 @@ void parse_response(response_t *response, void *wait_seconds) {
335
388
336
389
timestamp = json_object_get (optimal , "timestamp" );
337
390
if (!json_is_string (timestamp )) {
338
- fprintf (stderr ,
339
- "error: forecast %d optimalDataPoints %d timestamp is not a "
340
- "string\n" ,
341
- (int )i , (int )0 );
342
- json_decref (root );
343
- return ;
391
+ // might be cli with different name
392
+ timestamp = json_object_get (optimal , "Time" );
393
+ if (!json_is_string (timestamp )) {
394
+ fprintf (stderr ,
395
+ "error: forecast %d optimalDataPoints %d timestamp is not a "
396
+ "string\n" ,
397
+ (int )i , (int )0 );
398
+ json_decref (root );
399
+ return ;
400
+ }
344
401
}
345
402
346
403
timestamp_text = json_string_value (timestamp );
@@ -380,11 +437,18 @@ int main(int argc, char *argv[]) {
380
437
return 1 ;
381
438
}
382
439
383
- char url [URL_SIZE ];
384
- format_url (& params , url );
385
-
386
440
double wait_seconds = - DBL_MAX ;
387
- call_api (url , parse_response , & wait_seconds );
441
+ if (params .cli ) {
442
+ char cli [CLI_SIZE ];
443
+ format_params (& params , ISO8601_CLI_SIZE , ISO8601_CLI_FORMAT , CLI_SIZE ,
444
+ CLI_FORMAT , cli );
445
+ call_cli (cli , parse_response , & wait_seconds );
446
+ } else {
447
+ char url [URL_SIZE ];
448
+ format_params (& params , ISO8601_URL_SIZE , ISO8601_URL_FORMAT , URL_SIZE ,
449
+ URL_FORMAT , url );
450
+ call_api (url , parse_response , & wait_seconds );
451
+ }
388
452
389
453
if (wait_seconds > 5 * 60 ) {
390
454
printf ("Sleeping for %.2f minutes\n" , wait_seconds / 60 );
0 commit comments