55#include <stdio.h>
66#include <stdlib.h>
77#include <string.h>
8+ #include <sys/stat.h>
89#include <unistd.h>
910
1011#include <curl/curl.h>
1617 "%s/emissions/forecasts/current?location=%s&dataEndAt=%s&windowSize=%d"
1718#define URL_SIZE 256
1819
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+
1925typedef struct Params_t {
26+ bool cli ;
2027 char * url ;
2128 char * location ;
2229 int window ;
@@ -74,6 +81,7 @@ void home_path(char *file, char *path) {
7481
7582void default_args (params_t * params ) {
7683 // copy strings so they can be freed if overridden by config files
84+ params -> cli = false;
7785 params -> url = strdup ("https://carbon-aware-api.azurewebsites.net" );
7886 params -> location = strdup ("eastus" );
7987 params -> window = 30 ;
@@ -179,6 +187,14 @@ bool parse_args(int argc, char *argv[], params_t *params) {
179187 }
180188 i ++ ;
181189
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+
182198 // anything else is the optional command and its params
183199 params -> command = i ;
184200 return true;
@@ -196,34 +212,36 @@ the program will just block until the best time.\n\n",
196212 printf ("OPTIONS:\n\
197213 -l <location> specify location to check for carbon intensity\n\
198214 -d <duration> estimated window of runtime of command/task in minutes\n\
199- -u <api url> url prefix of Carbon Aware API server to consult\n" );
215+ -u <api url> url prefix of Carbon Aware API server to consult OR\n\
216+ full path to Carbon Aware CLI executable\n" );
200217}
201218
202- void format_url (params_t * params , char * url ) {
219+ void format_params (params_t * params , size_t iso8601_size , char * iso8601_format ,
220+ size_t len , char * format , char * param_str ) {
203221 time_t now ;
204222 struct tm * time_tm ;
205- char end [ISO8601_URL_SIZE ];
223+ char end [iso8601_size ];
206224 int size ;
207225
208226 time (& now );
209227 // add time this way isn't necessarily portable!
210228 now = now + params -> hours * 60 * 60 ;
211229 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 );
230+ size = snprintf (end , iso8601_size , iso8601_format ,
231+ time_tm -> tm_year + 1900 , // years from 1900
232+ time_tm -> tm_mon + 1 , // 0-based
233+ time_tm -> tm_mday , // 1-based
234+ time_tm -> tm_hour , time_tm -> tm_min , 0 );
217235
218- if (size > ISO8601_URL_SIZE ) {
236+ if (size > iso8601_size ) {
219237 // date string was truncated which should never happen
220238 fprintf (stderr , "Warning: end date corrupted (iso8601 date truncated)\n" );
221239 }
222240
223241 printf ("Requesting %d min duration window before %02d:%02d UTC in %s\n" ,
224242 params -> window , time_tm -> tm_hour , time_tm -> tm_min , params -> location );
225243
226- snprintf (url , URL_SIZE , URL_FORMAT , params -> url , params -> location , end ,
244+ snprintf (param_str , len , format , params -> url , params -> location , end ,
227245 params -> window );
228246}
229247
@@ -240,7 +258,7 @@ static size_t write_response(void *contents, size_t size, size_t nmemb,
240258 char * ptr = realloc (mem -> text , mem -> size + realsize + 1 );
241259 if (!ptr ) {
242260 // out of memory!
243- printf ( "not enough memory (realloc returned NULL)\n" );
261+ fprintf ( stderr , "not enough memory (realloc returned NULL)\n" );
244262 return 0 ;
245263 }
246264
@@ -252,7 +270,8 @@ static size_t write_response(void *contents, size_t size, size_t nmemb,
252270 return realsize ;
253271}
254272
255- void call_api (char * url , void (* extra_data )(response_t * , void * ), void * data ) {
273+ void call_api (char * url , void (* extract_data )(response_t * , void * ),
274+ void * data ) {
256275 CURL * curl ;
257276 CURLcode res ;
258277 response_t response ;
@@ -273,7 +292,7 @@ void call_api(char *url, void (*extra_data)(response_t *, void *), void *data) {
273292 fprintf (stderr , "curl_easy_perform() failed: %s\n" ,
274293 curl_easy_strerror (res ));
275294 } else {
276- extra_data (& response , data );
295+ extract_data (& response , data );
277296 }
278297 curl_easy_cleanup (curl );
279298 }
@@ -283,12 +302,43 @@ void call_api(char *url, void (*extra_data)(response_t *, void *), void *data) {
283302 curl_global_cleanup ();
284303}
285304
305+ void call_cli (char * cmd , void (* extract_data )(response_t * , void * ),
306+ void * data ) {
307+ const size_t BUFSIZE = 255 ;
308+ response_t response ;
309+
310+ response .text = malloc (1 ); // will be grown as needed by the realloc below
311+ response .size = 0 ; // no data at this point
312+
313+ char buf [BUFSIZE ];
314+ FILE * fp ;
315+
316+ if ((fp = popen (cmd , "r" )) == NULL ) {
317+ fprintf (stderr , "Error running CLI command\n" );
318+ return ;
319+ }
320+
321+ while (fgets (buf , BUFSIZE , fp ) != NULL ) {
322+ size_t read = strnlen (buf , BUFSIZE );
323+ write_response (buf , sizeof (char ), read , & response );
324+ }
325+
326+ if (pclose (fp )) {
327+ fprintf (stderr , "Command not found or exited with error status\n" );
328+ return ;
329+ }
330+
331+ extract_data (& response , data );
332+
333+ free (response .text );
334+ }
335+
286336void parse_response (response_t * response , void * wait_seconds ) {
287337 size_t i ;
288338 json_t * root ;
289339 json_error_t error ;
290340
291- printf ("Carbon Aware API returned %lu bytes\n" ,
341+ printf ("Carbon Aware SDK returned %lu bytes\n" ,
292342 (unsigned long )response -> size );
293343 root = json_loads (response -> text , 0 , & error );
294344
@@ -317,9 +367,13 @@ void parse_response(response_t *response, void *wait_seconds) {
317367
318368 optimals = json_object_get (data , "optimalDataPoints" );
319369 if (!json_is_array (optimals )) {
320- fprintf (stderr , "error: optimalDataPoints is not an array\n" );
321- json_decref (root );
322- return ;
370+ // might be cli and in capitals
371+ optimals = json_object_get (data , "OptimalDataPoints" );
372+ if (!json_is_array (optimals )) {
373+ fprintf (stderr , "error: optimalDataPoints is not an array\n" );
374+ json_decref (root );
375+ return ;
376+ }
323377 }
324378
325379 // just look at the first for now
@@ -335,12 +389,16 @@ void parse_response(response_t *response, void *wait_seconds) {
335389
336390 timestamp = json_object_get (optimal , "timestamp" );
337391 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 ;
392+ // might be cli with different name
393+ timestamp = json_object_get (optimal , "Time" );
394+ if (!json_is_string (timestamp )) {
395+ fprintf (stderr ,
396+ "error: forecast %d optimalDataPoints %d timestamp is not a "
397+ "string\n" ,
398+ (int )i , (int )0 );
399+ json_decref (root );
400+ return ;
401+ }
344402 }
345403
346404 timestamp_text = json_string_value (timestamp );
@@ -380,11 +438,18 @@ int main(int argc, char *argv[]) {
380438 return 1 ;
381439 }
382440
383- char url [URL_SIZE ];
384- format_url (& params , url );
385-
386441 double wait_seconds = - DBL_MAX ;
387- call_api (url , parse_response , & wait_seconds );
442+ if (params .cli ) {
443+ char cli [CLI_SIZE ];
444+ format_params (& params , ISO8601_CLI_SIZE , ISO8601_CLI_FORMAT , CLI_SIZE ,
445+ CLI_FORMAT , cli );
446+ call_cli (cli , parse_response , & wait_seconds );
447+ } else {
448+ char url [URL_SIZE ];
449+ format_params (& params , ISO8601_URL_SIZE , ISO8601_URL_FORMAT , URL_SIZE ,
450+ URL_FORMAT , url );
451+ call_api (url , parse_response , & wait_seconds );
452+ }
388453
389454 if (wait_seconds > 5 * 60 ) {
390455 printf ("Sleeping for %.2f minutes\n" , wait_seconds / 60 );
0 commit comments