Skip to content
This repository has been archived by the owner on Sep 27, 2023. It is now read-only.

Add server subcommand and gldserver module #1292

Merged
merged 21 commits into from
May 31, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ $(PYENV): requirements.txt
@python$(PYVER)-config --prefix 1>/dev/null || ( echo "ERROR [Makefile]: python$(PYVER)-dev is not installed" > /dev/stderr ; false )
@echo "Processing python requirements..."
@mkdir -p $(PYBIN) $(PYLIB) $(PYINC)
@(deactivate >/dev/null || true ; $(SYSPYTHON) -m venv $(PYENV))
@(deactivate 1>/dev/null 2>&1 || true ; $(SYSPYTHON) -m venv $(PYENV))
@$(ENVPYTHON) --version 1>/dev/null || ( echo "ERROR [Makefile]: $(ENVPYTHON) is not found" > /dev/stderr ; false )
@$(ENVPYTHON) -m pip install --upgrade pip
@$(ENVPYTHON) -m pip install pandas==2.0.0
Expand Down
4 changes: 2 additions & 2 deletions source/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -740,8 +740,8 @@ int ppolls(struct s_pipes *pipes, FILE* input_stream, FILE* output_stream, FILE
if ( polldata[0].revents&POLLNVAL )
{
// fprintf(stderr,"poll() pipe 0 invalid\n"); fflush(stderr);
output_error("GldMain::subcommand(command='%s'): input pipe invalid", pipes->child_command);
has_error = true;
output_warning("GldMain::subcommand(command='%s'): input pipe invalid", pipes->child_command);
// has_error = true;
break;
}
if ( polldata[1].revents&POLLNVAL )
Expand Down
10 changes: 5 additions & 5 deletions source/object.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2461,27 +2461,27 @@ int object_property_getsize(OBJECT *obj, PROPERTY *prop)
// dynamic size
PROPERTYSPEC *spec = property_getspec(prop->ptype);
int len = spec->csize;
IN_MYCONTEXT output_debug("object_property_getsize(OBJECT *obj={'name':'%s'}, PROPERTY *prop={'name':'%s','type':'%s'}): prop->width = %d", object_name(obj), prop->name, property_getspec(prop->ptype)->name, len);
// IN_MYCONTEXT output_debug("object_property_getsize(OBJECT *obj={'name':'%s'}, PROPERTY *prop={'name':'%s','type':'%s'}): prop->width = %d", object_name(obj), prop->name, property_getspec(prop->ptype)->name, len);
if ( len == PSZ_DYNAMIC )
{
len = property_write(prop,(char*)(obj+1)+(int64_t)(prop->addr),NULL,0);
IN_MYCONTEXT output_debug("object_property_getsize(OBJECT *obj={'name':'%s'}, PROPERTY *prop={'name':'%s'}) -> len = PSZ_DYNAMIC => len = %d", object_name(obj), prop->name, len);
// IN_MYCONTEXT output_debug("object_property_getsize(OBJECT *obj={'name':'%s'}, PROPERTY *prop={'name':'%s'}) -> len = PSZ_DYNAMIC => len = %d", object_name(obj), prop->name, len);
}
else if ( len == PSZ_AUTO )
{
// TODO: support general calls to underlying class implementing the property
std::string *str = (std::string*)(char*)(obj+1)+(int64_t)(prop->addr);
len = str->size()+1;
IN_MYCONTEXT output_debug("object_property_getsize(OBJECT *obj={'name':'%s'}, PROPERTY *prop={'name':'%s'}) -> len = PSZ_AUTO => len = %d", object_name(obj), prop->name, len);
// IN_MYCONTEXT output_debug("object_property_getsize(OBJECT *obj={'name':'%s'}, PROPERTY *prop={'name':'%s'}) -> len = PSZ_AUTO => len = %d", object_name(obj), prop->name, len);
}
if ( len < 0 )
{
IN_MYCONTEXT output_debug("object_property_getsize(OBJECT *obj={'name':'%s'}, PROPERTY *prop={'name':'%s'}) -> len = %d => len = 0", object_name(obj), prop->name, len);
// IN_MYCONTEXT output_debug("object_property_getsize(OBJECT *obj={'name':'%s'}, PROPERTY *prop={'name':'%s'}) -> len = %d => len = 0", object_name(obj), prop->name, len);
len = 0;
}
else
{
IN_MYCONTEXT output_debug("object_property_getsize(OBJECT *obj={'name':'%s'}, PROPERTY *prop={'name':'%s'}) -> len = %d", object_name(obj), prop->name, len);
// IN_MYCONTEXT output_debug("object_property_getsize(OBJECT *obj={'name':'%s'}, PROPERTY *prop={'name':'%s'}) -> len = %d", object_name(obj), prop->name, len);
}

return len;
Expand Down
42 changes: 36 additions & 6 deletions source/server.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1124,17 +1124,15 @@ int http_find_request(HTTPCNX *http,char *uri)
return 0;
http_format(http,"[\n");
obj = find_first(list);
while ( 1 )
while ( obj )
{
if ( obj->name == NULL )
http_format(http,"\t{\"name\" : \"%s:%d\"}",obj->oclass->name,obj->id);
else
http_format(http,"\t{\"name\" : \"%s\"}",obj->name);
obj = find_next(list,obj);
if ( obj!=NULL )
if ( obj != NULL )
http_format(http,",\n\t");
else
break;
}
http_format(http,"\n\t]\n");
http_type(http,"text/json");
Expand Down Expand Up @@ -1207,6 +1205,37 @@ int http_read_request(HTTPCNX *http, char *uri)
return 1;
}

/** Process a utility operation
* @returns non-zero on success, 0 on failure (errno set)
**/

int http_util_request(HTTPCNX *http, char *uri)
{
char token[64], value[1024];
if ( sscanf(uri,"%[^/]/%[^\n]",token,value) < 2 )
{
return 1;
}
if ( strcmp(token,"convert_to_timestamp") == 0 )
{
http_type(http,"text/html");
http_decode(value);
http_format(http,"%lld",convert_to_timestamp(value));
return 1;
}
else if ( strcmp(token,"convert_from_timestamp") == 0 )
{
http_type(http,"text/html");
char buffer[64];
http_format(http,"%s",convert_from_timestamp(atol(value),buffer,sizeof(buffer)-1)>0?buffer:"INVALID");
return 1;
}
else
{
http_format(http,"uri '%s/%s' is not valid",token,value);
return 9;
}
}
/** Process an incoming GUI request
@returns non-zero on success, 0 on failure (errno set)
**/
Expand Down Expand Up @@ -2023,9 +2052,10 @@ void *http_response(void *ptr)
{"/octave/", http_run_octave, HTTP_OK, HTTP_NOTFOUND},
{"/kml/", http_kml_request, HTTP_OK, HTTP_NOTFOUND},
{"/json/", http_json_request, HTTP_OK, HTTP_NOTFOUND},
{"/find/", http_find_request, HTTP_OK, HTTP_NOTFOUND},
{"/find/", http_find_request, HTTP_OK, HTTP_NOTFOUND},
{"/modify/", http_modify_request, HTTP_OK, HTTP_NOTFOUND},
{"/read/", http_read_request, HTTP_OK, HTTP_NOTFOUND},
{"/read/", http_read_request, HTTP_OK, HTTP_NOTFOUND},
{"/util/", http_util_request, HTTP_OK, HTTP_NOTFOUND},
};
size_t n;
for ( n=0 ; n<sizeof(map)/sizeof(map[0]) ; n++ )
Expand Down
1 change: 1 addition & 0 deletions subcommands/Makefile.mk
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ bin_SCRIPTS += subcommands/gridlabd-plot
bin_SCRIPTS += subcommands/gridlabd-python
bin_SCRIPTS += subcommands/gridlabd-require
bin_SCRIPTS += subcommands/gridlabd-requirements
bin_SCRIPTS += subcommands/gridlabd-server
bin_SCRIPTS += subcommands/gridlabd-template
bin_SCRIPTS += subcommands/gridlabd-timezone
bin_SCRIPTS += subcommands/gridlabd-trace
Expand Down
227 changes: 227 additions & 0 deletions subcommands/gridlabd-server
Original file line number Diff line number Diff line change
@@ -0,0 +1,227 @@
#!/bin/bash
## GridLAB-D server control
##
## Syntax: gridlabd server COMMAND
##
## Commands:
##
## start [-p|--port PORT] [-d|--detach] OPTIONS
##
## stop|halt|shutdown|kill PORT|all
##
## list [PORT|all]
##
## log PORT
##
## status [-c|--continuous] [PORT|all]
##
## The `gridlabd server` commands controls a server in the background. This is
## contrast to the `gridlabd --server` command which does not detach the
## process from the current process after it is launched.
##
## All servers are identified by their port number. The default port number is
## 6267 and incremented automatically is already in use. The maximum number
## of server that can be started automatically is 10.
##
## The `start` command requires the GridLAB-D command line options to be
## provided.
##
## The `stop`, `halt`, `shutdown`, and `kill` commands correspond to the
## GridLAB-D server control commands. See [[GLM/Server]] for details.
##
## The `list` command outputs a list of active servers. The `status` command
## outputs the status of active servers.
##
## The server status can be one of the following:
##
## INIT initialization in progress
## RUNNING simulation running
## PAUSED simulation paused
## DONE simulation done
## LOCKED simulation locked for concurrency
## NOREPLY simulation server not responding to control commands
##
## See also:
##
## [[GLM/Server]]
##

E_OK=0
E_FAILED=1
E_INVALID=2
E_MISSING=3
E_NOTFOUND=4
E_SYNTAX=9

TIMEOUT=10
MAXPORTS=10
LOGFILE=/tmp/gridlabd-server

function error ()
{
RC=$1
shift 1
echo "ERROR [gridlabd-server]: $*" > /dev/stderr
exit $RC
}

function getport ()
{
if [ $# -eq 0 ]; then
PORT=6267
while [ ! -z "$(curl -s http://localhost:$PORT/raw/mainloop_state 2>/dev/null)" ]; do
PORT=$(($PORT+1))
done
echo $PORT
else
echo $(($1+6266))
fi
}

function getstatus()
{
for P in $(list $*); do
echo $P $(curl -s http://localhost:$P/raw/mainloop_state 2>/dev/null || echo "NOREPLY")
done

}

function waitport ()
{
for P in $(list $*); do
T=0
while [ ! -z "$(curl -s http://localhost:$P/raw/mainloop_state 2>/dev/null)" ]; do
sleep 1
T=$(($T+1))
if [ $T -gt $TIMEOUT ]; then
error E_FAILED "wait on port $P timeout"
fi
done
done
}

function list ()
{
if [ $# -gt 0 ]; then
for P in $*; do
curl -s http://localhost:$P/raw/mainloop_state >/dev/null && echo $P
done
else
P=0
while [ $P -lt $MAXPORTS ]; do
PORT=$(($P+6267))
curl -s http://localhost:$PORT/raw/mainloop_state >/dev/null && echo $PORT
P=$(($P+1))
done
fi
}

# catch no args
if [ $# -eq 0 ]; then
grep '^## ' $0 | cut -c4- | grep '^Syntax: ' > /dev/stderr
exit $E_SYNTAX
fi

# process args
case $1 in
help | --help | -h )
grep '^## ' $0 | cut -c4-
;;
start )
shift 1
while [ -z "$OK" ]; do
case $1 in
-p | --port )
PORT=$2
shift 2
;;
-d | --detach )
DETACH=yes
shift 1
;;
-l | --logfile )
LOG=$2
shift 2
;;
* )
OK=yes
;;
esac
done
if [ -z "$PORT" ]; then
PORT=$(getport)
echo $PORT
fi
if [ -z "$LOG" ]; then
LOG=$LOGFILE-$PORT.log
elif [ "$LOG" = "-" ]; then
LOG=/dev/stderr
fi
rm -f $LOG
gridlabd --server -D server_portnum=$PORT $* 1>$LOG 2>&1 &
sleep 1
if [ ! -z "$!" ] ; then
if [ ! -z "$DETACH" ]; then
disown %1
fi
else
error $E_FAILED "unable to start server for port $PORT"
fi
;;
stop | halt | shutdown | kill )
if [ -z "$2" ]; then
error $E_MISSING "missing port number"
elif [ "$2" = "all" ]; then
PORTLIST=$(list)
else
PORTLIST=$*
fi
for PORT in $PORTLIST; do
curl -s http://localhost:$PORT/control/$1
done
waitport $PORTLIST
;;
list )
shift 1
list $*
;;
log )
shift 1
if [ $# -eq 0 ]; then
error $E_MISSING "missing port number"
elif [ ! -f $LOGFILE-$1.log ]; then
error $E_NOTFOUND "no log found for port $1"
fi
echo 'INFO [gridlabd-server]: created '$(stat -f %SB -t "%Y-%m-%d %H:%M:%S %Z" $LOGFILE-$1.log)
cat $LOGFILE-$1.log
if [ -z "$(getstatus $1)" ]; then
echo 'INFO [gridlabd-server]: stopped '$(stat -f %Sm -t "%Y-%m-%d %H:%M:%S %Z" $LOGFILE-$1.log)
else
echo 'INFO [gridlabd-server]: updated '$(stat -f %Sm -t "%Y-%m-%d %H:%M:%S %Z" $LOGFILE-$1.log)
fi
;;
status )
shift 1
if [ "$1" = "-c" -o "$1" = "--continuous" ]; then
shift 1
while [ true ]; do
clear
echo "Server Last update Status Options"
echo "------ ------------------- ---------- --------------------"
for P in $(list $*); do
printf '%6.6s %19.19s %10.10s %s\n' "$P" "$(stat -f %Sm -t "%Y-%m-%d %H:%M:%S" $LOGFILE-$P.log)" "$(curl -s http://localhost:$P/raw/mainloop_state 2>/dev/null || echo 'NOREPLY')" "$(curl -s http://localhost:$P/raw/command_line 2>/dev/null | cut -f5- -d' ')"
done
echo ""
echo "Press Ctrl-C to quit"
sleep 1
done
else
getstatus $*
fi
;;
* )
error $E_INVALID "'$1' is an invalid gridlabd-server command"
;;
esac
exit $E_OK

1 change: 1 addition & 0 deletions tools/Makefile.mk
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ dist_pkgdata_DATA += tools/market_model.py
dist_pkgdata_DATA += tools/meteostat_weather.py
dist_pkgdata_DATA += tools/metar2glm.py
dist_pkgdata_DATA += tools/read_dlp.py
dist_pkgdata_DATA += tools/gldserver.py
dist_pkgdata_DATA += tools/noaa_forecast.py
dist_pkgdata_DATA += tools/nsrdb_weather.py
dist_pkgdata_DATA += tools/ucar_weather.py
Expand Down
Loading