examples/power: add JSON string handling

Add JSON string handling to vm_power_manager for JSON strings received
through the fifo. The format of the JSON strings are detailed in the
next patch, the vm_power_manager user guide documentation updates.

This patch introduces a new dependency on Jansson, a C library for
encoding, decoding and manipulating JSON data. To compile the sample app
you now need to have installed libjansson4 and libjansson-dev (these may
be named slightly differently depending on your Operating System)

Signed-off-by: David Hunt <david.hunt@intel.com>
Acked-by: Anatoly Burakov <anatoly.burakov@intel.com>
This commit is contained in:
David Hunt 2018-10-17 14:05:31 +01:00 committed by Thomas Monjalon
parent 90a774c479
commit a63504a90f
4 changed files with 624 additions and 25 deletions

View File

@ -200,6 +200,14 @@ New Features
See the :doc:`../prog_guide/power_man` section of the DPDK Programmers
Guide document for more information.
* **Added JSON power policy interface for containers.**
Extended the Power Library and vm_power_manager sample app to allow power
policies to be submitted via a FIFO using JSON formatted strings. Previously
limited to Virtual Machines, this feature extends power policy functionality
to containers and host applications that need to have their cores frequency
controlled based on the rules contained in the policy.
* **Added ability to switch queue deferred start flag on testpmd app.**
Added a console command to testpmd app, giving ability to switch

View File

@ -337,6 +337,270 @@ monitoring of branch ratio on cores doing busy polling via PMDs.
and will need to be adjusted for different workloads.
JSON API
~~~~~~~~
In addition to the command line interface for host command and a virtio-serial
interface for VM power policies, there is also a JSON interface through which
power commands and policies can be sent. This functionality adds a dependency
on the Jansson library, and the Jansson development package must be installed
on the system before the JSON parsing functionality is included in the app.
This is achieved by:
.. code-block:: javascript
apt-get install libjansson-dev
The command and package name may be different depending on your operating
system. It's worth noting that the app will successfully build without this
package present, but a warning is shown during compilation, and the JSON
parsing functionality will not be present in the app.
Sending a command or policy to the power manager application is achieved by
simply opening a fifo file, writing a JSON string to that fifo, and closing
the file.
The fifo is at /tmp/powermonitor/fifo
The jason string can be a policy or instruction, and takes the following
format:
.. code-block:: javascript
{"packet_type": {
"pair_1": value,
"pair_2": value
}}
The 'packet_type' header can contain one of two values, depending on
whether a policy or power command is being sent. The two possible values are
"policy" and "instruction", and the expected name-value pairs is different
depending on which type is being sent.
The pairs are the format of standard JSON name-value pairs. The value type
varies between the different name/value pairs, and may be integers, strings,
arrays, etc. Examples of policies follow later in this document. The allowed
names and value types are as follows:
:Pair Name: "name"
:Description: Name of the VM or Host. Allows the parser to associate the
policy with the relevant VM or Host OS.
:Type: string
:Values: any valid string
:Required: yes
:Example:
.. code-block:: javascript
"name", "ubuntu2"
:Pair Name: "command"
:Description: The type of packet we're sending to the power manager. We can be
creating or destroying a policy, or sending a direct command to adjust
the frequency of a core, similar to the command line interface.
:Type: string
:Values:
:CREATE: used when creating a new policy,
:DESTROY: used when removing a policy,
:POWER: used when sending an immediate command, max, min, etc.
:Required: yes
:Example:
.. code-block:: javascript
"command", "CREATE"
:Pair Name: "policy_type"
:Description: Type of policy to apply. Please see vm_power_manager documentation
for more information on the types of policies that may be used.
:Type: string
:Values:
:TIME: Time-of-day policy. Frequencies of the relevant cores are
scaled up/down depending on busy and quiet hours.
:TRAFFIC: This policy takes statistics from the NIC and scales up
and down accordingly.
:WORKLOAD: This policy looks at how heavily loaded the cores are,
and scales up and down accordingly.
:BRANCH_RATIO: This out-of-band policy can look at the ratio between
branch hits and misses on a core, and is useful for detecting
how much packet processing a core is doing.
:Required: only for CREATE/DESTROY command
:Example:
.. code-block:: javascript
"policy_type", "TIME"
:Pair Name: "busy_hours"
:Description: The hours of the day in which we scale up the cores for busy
times.
:Type: array of integers
:Values: array with list of hour numbers, (0-23)
:Required: only for TIME policy
:Example:
.. code-block:: javascript
"busy_hours":[ 17, 18, 19, 20, 21, 22, 23 ]
:Pair Name: "quiet_hours"
:Description: The hours of the day in which we scale down the cores for quiet
times.
:Type: array of integers
:Values: array with list of hour numbers, (0-23)
:Required: only for TIME policy
:Example:
.. code-block:: javascript
"quiet_hours":[ 2, 3, 4, 5, 6 ]
:Pair Name: "avg_packet_thresh"
:Description: Threshold below which the frequency will be set to min for
the TRAFFIC policy. If the traffic rate is above this and below max, the
frequency will be set to medium.
:Type: integer
:Values: The number of packets below which the TRAFFIC policy applies the
minimum frequency, or medium frequency if between avg and max thresholds.
:Required: only for TRAFFIC policy
:Example:
.. code-block:: javascript
"avg_packet_thresh": 100000
:Pair Name: "max_packet_thresh"
:Description: Threshold above which the frequency will be set to max for
the TRAFFIC policy
:Type: integer
:Values: The number of packets per interval above which the TRAFFIC policy
applies the maximum frequency
:Required: only for TRAFFIC policy
:Example:
.. code-block:: javascript
"max_packet_thresh": 500000
:Pair Name: "core_list"
:Description: The cores to which to apply the policy.
:Type: array of integers
:Values: array with list of virtual CPUs.
:Required: only policy CREATE/DESTROY
:Example:
.. code-block:: javascript
"core_list":[ 10, 11 ]
:Pair Name: "workload"
:Description: When our policy is of type WORKLOAD, we need to specify how
heavy our workload is.
:Type: string
:Values:
:HIGH: For cores running workloads that require high frequencies
:MEDIUM: For cores running workloads that require medium frequencies
:LOW: For cores running workloads that require low frequencies
:Required: only for WORKLOAD policy types
:Example:
.. code-block:: javascript
"workload", "MEDIUM"
:Pair Name: "mac_list"
:Description: When our policy is of type TRAFFIC, we need to specify the
MAC addresses that the host needs to monitor
:Type: string
:Values: array with a list of mac address strings.
:Required: only for TRAFFIC policy types
:Example:
.. code-block:: javascript
"mac_list":[ "de:ad:be:ef:01:01", "de:ad:be:ef:01:02" ]
:Pair Name: "unit"
:Description: the type of power operation to apply in the command
:Type: string
:Values:
:SCALE_MAX: Scale frequency of this core to maximum
:SCALE_MIN: Scale frequency of this core to minimum
:SCALE_UP: Scale up frequency of this core
:SCALE_DOWN: Scale down frequency of this core
:ENABLE_TURBO: Enable Turbo Boost for this core
:DISABLE_TURBO: Disable Turbo Boost for this core
:Required: only for POWER instruction
:Example:
.. code-block:: javascript
"unit", "SCALE_MAX"
:Pair Name: "resource_id"
:Description: The core to which to apply the power command.
:Type: integer
:Values: valid core id for VM or host OS.
:Required: only POWER instruction
:Example:
.. code-block:: javascript
"resource_id": 10
JSON API Examples
~~~~~~~~~~~~~~~~~
Profile create example:
.. code-block:: javascript
{"policy": {
"name": "ubuntu",
"command": "create",
"policy_type": "TIME",
"busy_hours":[ 17, 18, 19, 20, 21, 22, 23 ],
"quiet_hours":[ 2, 3, 4, 5, 6 ],
"core_list":[ 11 ]
}}
Profile destroy example:
.. code-block:: javascript
{"profile": {
"name": "ubuntu",
"command": "destroy",
}}
Power command example:
.. code-block:: javascript
{"command": {
"name": "ubuntu",
"unit": "SCALE_MAX",
"resource_id": 10
}}
To send a JSON string to the Power Manager application, simply paste the
example JSON string into a text file and cat it into the fifo:
.. code-block:: console
cat file.json >/tmp/powermonitor/fifo
The console of the Power Manager application should indicate the command that
was just received via the fifo.
Compiling and Running the Guest Applications
--------------------------------------------

View File

@ -31,6 +31,12 @@ CFLAGS += $(WERROR_FLAGS)
LDLIBS += -lvirt
JANSSON := $(shell pkg-config --exists jansson; echo $$?)
ifeq ($(JANSSON), 0)
LDLIBS += $(shell pkg-config --libs jansson)
CFLAGS += -DUSE_JANSSON
endif
ifeq ($(CONFIG_RTE_BUILD_SHARED_LIB),y)
ifeq ($(CONFIG_RTE_LIBRTE_IXGBE_PMD),y)

View File

@ -9,11 +9,18 @@
#include <signal.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/epoll.h>
#include <sys/queue.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <sys/select.h>
#ifdef USE_JANSSON
#include <jansson.h>
#else
#pragma message "Jansson dev libs unavailable, not including JSON parsing"
#endif
#include <rte_log.h>
#include <rte_memory.h>
#include <rte_malloc.h>
@ -35,6 +42,8 @@
uint64_t vsi_pkt_count_prev[384];
uint64_t rdtsc_prev[384];
#define MAX_JSON_STRING_LEN 1024
char json_data[MAX_JSON_STRING_LEN];
double time_period_ms = 1;
static volatile unsigned run_loop = 1;
@ -43,6 +52,234 @@ static unsigned int policy_is_set;
static struct epoll_event *global_events_list;
static struct policy policies[MAX_CLIENTS];
#ifdef USE_JANSSON
union PFID {
struct ether_addr addr;
uint64_t pfid;
};
static int
str_to_ether_addr(const char *a, struct ether_addr *ether_addr)
{
int i;
char *end;
unsigned long o[ETHER_ADDR_LEN];
i = 0;
do {
errno = 0;
o[i] = strtoul(a, &end, 16);
if (errno != 0 || end == a || (end[0] != ':' && end[0] != 0))
return -1;
a = end + 1;
} while (++i != RTE_DIM(o) / sizeof(o[0]) && end[0] != 0);
/* Junk at the end of line */
if (end[0] != 0)
return -1;
/* Support the format XX:XX:XX:XX:XX:XX */
if (i == ETHER_ADDR_LEN) {
while (i-- != 0) {
if (o[i] > UINT8_MAX)
return -1;
ether_addr->addr_bytes[i] = (uint8_t)o[i];
}
/* Support the format XXXX:XXXX:XXXX */
} else if (i == ETHER_ADDR_LEN / 2) {
while (i-- != 0) {
if (o[i] > UINT16_MAX)
return -1;
ether_addr->addr_bytes[i * 2] =
(uint8_t)(o[i] >> 8);
ether_addr->addr_bytes[i * 2 + 1] =
(uint8_t)(o[i] & 0xff);
}
/* unknown format */
} else
return -1;
return 0;
}
static int
set_policy_mac(struct channel_packet *pkt, int idx, char *mac)
{
union PFID pfid;
int ret;
/* Use port MAC address as the vfid */
ret = str_to_ether_addr(mac, &pfid.addr);
if (ret != 0) {
RTE_LOG(ERR, CHANNEL_MONITOR,
"Invalid mac address received in JSON\n");
pkt->vfid[idx] = 0;
return -1;
}
printf("Received MAC Address: %02" PRIx8 ":%02" PRIx8 ":%02" PRIx8 ":"
"%02" PRIx8 ":%02" PRIx8 ":%02" PRIx8 "\n",
pfid.addr.addr_bytes[0], pfid.addr.addr_bytes[1],
pfid.addr.addr_bytes[2], pfid.addr.addr_bytes[3],
pfid.addr.addr_bytes[4], pfid.addr.addr_bytes[5]);
pkt->vfid[idx] = pfid.pfid;
return 0;
}
static int
parse_json_to_pkt(json_t *element, struct channel_packet *pkt)
{
const char *key;
json_t *value;
int ret;
memset(pkt, 0, sizeof(struct channel_packet));
pkt->nb_mac_to_monitor = 0;
pkt->t_boost_status.tbEnabled = false;
pkt->workload = LOW;
pkt->policy_to_use = TIME;
pkt->command = PKT_POLICY;
pkt->core_type = CORE_TYPE_PHYSICAL;
json_object_foreach(element, key, value) {
if (!strcmp(key, "policy")) {
/* Recurse in to get the contents of profile */
ret = parse_json_to_pkt(value, pkt);
if (ret)
return ret;
} else if (!strcmp(key, "instruction")) {
/* Recurse in to get the contents of instruction */
ret = parse_json_to_pkt(value, pkt);
if (ret)
return ret;
} else if (!strcmp(key, "name")) {
strcpy(pkt->vm_name, json_string_value(value));
} else if (!strcmp(key, "command")) {
char command[32];
snprintf(command, 32, "%s", json_string_value(value));
if (!strcmp(command, "power")) {
pkt->command = CPU_POWER;
} else if (!strcmp(command, "create")) {
pkt->command = PKT_POLICY;
} else if (!strcmp(command, "destroy")) {
pkt->command = PKT_POLICY_REMOVE;
} else {
RTE_LOG(ERR, CHANNEL_MONITOR,
"Invalid command received in JSON\n");
return -1;
}
} else if (!strcmp(key, "policy_type")) {
char command[32];
snprintf(command, 32, "%s", json_string_value(value));
if (!strcmp(command, "TIME")) {
pkt->policy_to_use = TIME;
} else if (!strcmp(command, "TRAFFIC")) {
pkt->policy_to_use = TRAFFIC;
} else if (!strcmp(command, "WORKLOAD")) {
pkt->policy_to_use = WORKLOAD;
} else if (!strcmp(command, "BRANCH_RATIO")) {
pkt->policy_to_use = BRANCH_RATIO;
} else {
RTE_LOG(ERR, CHANNEL_MONITOR,
"Wrong policy_type received in JSON\n");
return -1;
}
} else if (!strcmp(key, "workload")) {
char command[32];
snprintf(command, 32, "%s", json_string_value(value));
if (!strcmp(command, "HIGH")) {
pkt->workload = HIGH;
} else if (!strcmp(command, "MEDIUM")) {
pkt->workload = MEDIUM;
} else if (!strcmp(command, "LOW")) {
pkt->workload = LOW;
} else {
RTE_LOG(ERR, CHANNEL_MONITOR,
"Wrong workload received in JSON\n");
return -1;
}
} else if (!strcmp(key, "busy_hours")) {
unsigned int i;
size_t size = json_array_size(value);
for (i = 0; i < size; i++) {
int hour = (int)json_integer_value(
json_array_get(value, i));
pkt->timer_policy.busy_hours[i] = hour;
}
} else if (!strcmp(key, "quiet_hours")) {
unsigned int i;
size_t size = json_array_size(value);
for (i = 0; i < size; i++) {
int hour = (int)json_integer_value(
json_array_get(value, i));
pkt->timer_policy.quiet_hours[i] = hour;
}
} else if (!strcmp(key, "core_list")) {
unsigned int i;
size_t size = json_array_size(value);
for (i = 0; i < size; i++) {
int core = (int)json_integer_value(
json_array_get(value, i));
pkt->vcpu_to_control[i] = core;
}
pkt->num_vcpu = size;
} else if (!strcmp(key, "mac_list")) {
unsigned int i;
size_t size = json_array_size(value);
for (i = 0; i < size; i++) {
char mac[32];
snprintf(mac, 32, "%s", json_string_value(
json_array_get(value, i)));
set_policy_mac(pkt, i, mac);
}
pkt->nb_mac_to_monitor = size;
} else if (!strcmp(key, "avg_packet_thresh")) {
pkt->traffic_policy.avg_max_packet_thresh =
(uint32_t)json_integer_value(value);
} else if (!strcmp(key, "max_packet_thresh")) {
pkt->traffic_policy.max_max_packet_thresh =
(uint32_t)json_integer_value(value);
} else if (!strcmp(key, "unit")) {
char unit[32];
snprintf(unit, 32, "%s", json_string_value(value));
if (!strcmp(unit, "SCALE_UP")) {
pkt->unit = CPU_POWER_SCALE_UP;
} else if (!strcmp(unit, "SCALE_DOWN")) {
pkt->unit = CPU_POWER_SCALE_DOWN;
} else if (!strcmp(unit, "SCALE_MAX")) {
pkt->unit = CPU_POWER_SCALE_MAX;
} else if (!strcmp(unit, "SCALE_MIN")) {
pkt->unit = CPU_POWER_SCALE_MIN;
} else if (!strcmp(unit, "ENABLE_TURBO")) {
pkt->unit = CPU_POWER_ENABLE_TURBO;
} else if (!strcmp(unit, "DISABLE_TURBO")) {
pkt->unit = CPU_POWER_DISABLE_TURBO;
} else {
RTE_LOG(ERR, CHANNEL_MONITOR,
"Invalid command received in JSON\n");
return -1;
}
} else if (!strcmp(key, "resource_id")) {
pkt->resource_id = (uint32_t)json_integer_value(value);
} else {
RTE_LOG(ERR, CHANNEL_MONITOR,
"Unknown key received in JSON string: %s\n",
key);
}
}
return 0;
}
#endif
void channel_monitor_exit(void)
{
run_loop = 0;
@ -555,6 +792,103 @@ channel_monitor_init(void)
return 0;
}
static void
read_binary_packet(struct channel_info *chan_info)
{
struct channel_packet pkt;
void *buffer = &pkt;
int buffer_len = sizeof(pkt);
int n_bytes, err = 0;
while (buffer_len > 0) {
n_bytes = read(chan_info->fd,
buffer, buffer_len);
if (n_bytes == buffer_len)
break;
if (n_bytes == -1) {
err = errno;
RTE_LOG(DEBUG, CHANNEL_MONITOR,
"Received error on "
"channel '%s' read: %s\n",
chan_info->channel_path,
strerror(err));
remove_channel(&chan_info);
break;
}
buffer = (char *)buffer + n_bytes;
buffer_len -= n_bytes;
}
if (!err)
process_request(&pkt, chan_info);
}
#ifdef USE_JANSSON
static void
read_json_packet(struct channel_info *chan_info)
{
struct channel_packet pkt;
int n_bytes, ret;
json_t *root;
json_error_t error;
/* read opening brace to closing brace */
do {
int idx = 0;
int indent = 0;
do {
n_bytes = read(chan_info->fd, &json_data[idx], 1);
if (n_bytes == 0)
break;
if (json_data[idx] == '{')
indent++;
if (json_data[idx] == '}')
indent--;
if ((indent > 0) || (idx > 0))
idx++;
if (indent == 0)
json_data[idx] = 0;
if (idx >= MAX_JSON_STRING_LEN-1)
break;
} while (indent > 0);
if (indent > 0)
/*
* We've broken out of the read loop without getting
* a closing brace, so throw away the data
*/
json_data[idx] = 0;
if (strlen(json_data) == 0)
continue;
printf("got [%s]\n", json_data);
root = json_loads(json_data, 0, &error);
if (root) {
/*
* Because our data is now in the json
* object, we can overwrite the pkt
* with a channel_packet struct, using
* parse_json_to_pkt()
*/
ret = parse_json_to_pkt(root, &pkt);
json_decref(root);
if (ret) {
RTE_LOG(ERR, CHANNEL_MONITOR,
"Error validating JSON profile data\n");
break;
}
process_request(&pkt, chan_info);
} else {
RTE_LOG(ERR, CHANNEL_MONITOR,
"JSON error on line %d: %s\n",
error.line, error.text);
}
} while (n_bytes > 0);
}
#endif
void
run_channel_monitor(void)
{
@ -578,31 +912,18 @@ run_channel_monitor(void)
}
if (global_events_list[i].events & EPOLLIN) {
int n_bytes, err = 0;
struct channel_packet pkt;
void *buffer = &pkt;
int buffer_len = sizeof(pkt);
while (buffer_len > 0) {
n_bytes = read(chan_info->fd,
buffer, buffer_len);
if (n_bytes == buffer_len)
break;
if (n_bytes == -1) {
err = errno;
RTE_LOG(DEBUG, CHANNEL_MONITOR,
"Received error on "
"channel '%s' read: %s\n",
chan_info->channel_path,
strerror(err));
remove_channel(&chan_info);
break;
}
buffer = (char *)buffer + n_bytes;
buffer_len -= n_bytes;
switch (chan_info->type) {
case CHANNEL_TYPE_BINARY:
read_binary_packet(chan_info);
break;
#ifdef USE_JANSSON
case CHANNEL_TYPE_JSON:
read_json_packet(chan_info);
break;
#endif
default:
break;
}
if (!err)
process_request(&pkt, chan_info);
}
}
rte_delay_us(time_period_ms*1000);