Skip to content

Commit

Permalink
Add support for tracking and logging daemon memory usage.
Browse files Browse the repository at this point in the history
Signed-off-by: Ben Pfaff <[email protected]>
  • Loading branch information
blp committed May 22, 2012
1 parent 44bac24 commit 0d08568
Show file tree
Hide file tree
Showing 26 changed files with 495 additions and 10 deletions.
2 changes: 2 additions & 0 deletions lib/automake.mk
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@ lib_libopenvswitch_a_SOURCES = \
lib/lockfile.h \
lib/mac-learning.c \
lib/mac-learning.h \
lib/memory.c \
lib/memory.h \
lib/meta-flow.c \
lib/meta-flow.h \
lib/multipath.c \
Expand Down
175 changes: 175 additions & 0 deletions lib/memory.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
/*
* Copyright (c) 2012 Nicira, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

#include <config.h>
#include "memory.h"
#include <stdbool.h>
#include <sys/time.h>
#include <sys/resource.h>
#include "dynamic-string.h"
#include "poll-loop.h"
#include "simap.h"
#include "timeval.h"
#include "unixctl.h"
#include "vlog.h"

VLOG_DEFINE_THIS_MODULE(memory);

/* The number of milliseconds before the first report of daemon memory usage,
* and the number of milliseconds between checks for daemon memory growth. */
#define MEMORY_CHECK_INTERVAL (10 * 1000)

/* When we should next check memory usage and possibly trigger a report. */
static long long int next_check;

/* The last time at which we reported memory usage, and the usage we reported
* at that time. */
static long long int last_report;
static unsigned long int last_reported_maxrss;

/* Are we expecting a call to memory_report()? */
static bool want_report;

/* Unixctl connections waiting for responses. */
static struct unixctl_conn **conns;
static size_t n_conns;

static void memory_init(void);

/* Runs the memory monitor.
*
* The client should call memory_should_report() afterward. */
void
memory_run(void)
{
struct rusage usage;
long long int now;

memory_init();

/* Time for a check? */
now = time_msec();
if (now < next_check) {
return;
}
next_check = now + MEMORY_CHECK_INTERVAL;

/* Time for a report? */
getrusage(RUSAGE_SELF, &usage);
if (!last_reported_maxrss) {
VLOG_INFO("%lu kB peak resident set size after %.1f seconds",
(unsigned long int) usage.ru_maxrss,
(now - time_boot_msec()) / 1000.0);
} else if (usage.ru_maxrss >= last_reported_maxrss * 1.5) {
VLOG_INFO("peak resident set size grew %.0f%% in last %.1f seconds, "
"from %lu kB to %lu kB",
((double) usage.ru_maxrss / last_reported_maxrss - 1) * 100,
(now - last_report) / 1000.0,
last_reported_maxrss, (unsigned long int) usage.ru_maxrss);
} else {
return;
}

/* Request a report. */
want_report = true;
last_report = now;
last_reported_maxrss = usage.ru_maxrss;
}

/* Causes the poll loop to wake up if the memory monitor needs to run. */
void
memory_wait(void)
{
if (memory_should_report()) {
poll_immediate_wake();
}
}

/* Returns true if the caller should log some information about memory usage
* (with memory_report()), false otherwise. */
bool
memory_should_report(void)
{
return want_report || n_conns > 0;
}

static void
compose_report(const struct simap *usage, struct ds *s)
{
const struct simap_node **nodes = simap_sort(usage);
size_t n = simap_count(usage);
size_t i;

for (i = 0; i < n; i++) {
const struct simap_node *node = nodes[i];

ds_put_format(s, "%s:%u ", node->name, node->data);
}
ds_chomp(s, ' ');
}

/* Logs the contents of 'usage', as a collection of name-count pairs.
*
* 'usage' should capture large-scale statistics that one might reasonably
* expect to correlate with memory usage. For example, each OpenFlow flow
* requires some memory, so ovs-vswitchd includes the total number of flows in
* 'usage'. */
void
memory_report(const struct simap *usage)
{
struct ds s;
size_t i;

ds_init(&s);
compose_report(usage, &s);

if (want_report) {
VLOG_INFO("%s", ds_cstr(&s));
want_report = false;
}
if (n_conns) {
for (i = 0; i < n_conns; i++) {
unixctl_command_reply(conns[i], ds_cstr(&s));
}
free(conns);
conns = NULL;
n_conns = 0;
}

ds_destroy(&s);
}

static void
memory_unixctl_show(struct unixctl_conn *conn, int argc OVS_UNUSED,
const char *argv[] OVS_UNUSED, void *aux OVS_UNUSED)
{
conns = xrealloc(conns, (n_conns + 1) * sizeof *conns);
conns[n_conns++] = conn;
}

static void
memory_init(void)
{
static bool inited = false;

if (!inited) {
inited = true;
unixctl_command_register("memory/show", "", 0, 0,
memory_unixctl_show, NULL);

next_check = time_boot_msec() + MEMORY_CHECK_INTERVAL;
}
}
60 changes: 60 additions & 0 deletions lib/memory.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*
* Copyright (c) 2012 Nicira, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

#ifndef MEMORY_H
#define MEMORY_H 1

/* Memory usage monitor.
*
* This is intended to be called as part of a daemon's main loop. After some
* time to allow the daemon to allocate an initial memory usage, it logs some
* memory usage information (most of which must actually be provided by the
* client). At intervals, if the daemon's memory usage has grown
* significantly, it again logs information.
*
* The monitor also has a unixctl interface.
*
* Intended usage in the program's main loop is like this:
*
* for (;;) {
* memory_run();
* if (memory_should_report()) {
* struct simap usage;
*
* simap_init(&usage);
* ...fill in 'usage' with meaningful statistics...
* memory_report(&usage);
* simap_destroy(&usage);
* }
*
* ...
*
* memory_wait();
* poll_block();
* }
*/

#include <stdbool.h>

struct simap;

void memory_run(void);
void memory_wait(void);

bool memory_should_report(void);
void memory_report(const struct simap *usage);

#endif /* memory.h */
7 changes: 7 additions & 0 deletions lib/rconn.c
Original file line number Diff line number Diff line change
Expand Up @@ -855,6 +855,13 @@ rconn_get_last_error(const struct rconn *rc)
{
return rc->last_error;
}

/* Returns the number of messages queued for transmission on 'rc'. */
unsigned int
rconn_count_txqlen(const struct rconn *rc)
{
return list_size(&rc->txq);
}

struct rconn_packet_counter *
rconn_packet_counter_create(void)
Expand Down
3 changes: 2 additions & 1 deletion lib/rconn.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2008, 2009, 2010, 2011 Nicira, Inc.
* Copyright (c) 2008, 2009, 2010, 2011, 2012 Nicira, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -91,6 +91,7 @@ int rconn_get_backoff(const struct rconn *);
unsigned int rconn_get_state_elapsed(const struct rconn *);
unsigned int rconn_get_connection_seqno(const struct rconn *);
int rconn_get_last_error(const struct rconn *);
unsigned int rconn_count_txqlen(const struct rconn *);

/* Counts the number of packets queued into an rconn by a given source. */
struct rconn_packet_counter {
Expand Down
25 changes: 25 additions & 0 deletions ofproto/connmgr.c
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
#include "pktbuf.h"
#include "rconn.h"
#include "shash.h"
#include "simap.h"
#include "stream.h"
#include "timeval.h"
#include "vconn.h"
Expand Down Expand Up @@ -338,6 +339,30 @@ connmgr_wait(struct connmgr *mgr, bool handling_openflow)
}
}

/* Adds some memory usage statistics for 'mgr' into 'usage', for use with
* memory_report(). */
void
connmgr_get_memory_usage(const struct connmgr *mgr, struct simap *usage)
{
const struct ofconn *ofconn;
unsigned int packets = 0;
unsigned int ofconns = 0;

LIST_FOR_EACH (ofconn, node, &mgr->all_conns) {
int i;

ofconns++;

packets += rconn_count_txqlen(ofconn->rconn);
for (i = 0; i < N_SCHEDULERS; i++) {
packets += pinsched_count_txqlen(ofconn->schedulers[i]);
}
packets += pktbuf_count_packets(ofconn->pktbuf);
}
simap_increase(usage, "ofconns", ofconns);
simap_increase(usage, "packets", packets);
}

/* Returns the ofproto that owns 'ofconn''s connmgr. */
struct ofproto *
ofconn_get_ofproto(const struct ofconn *ofconn)
Expand Down
3 changes: 3 additions & 0 deletions ofproto/connmgr.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ struct ofopgroup;
struct ofputil_flow_removed;
struct ofputil_packet_in;
struct ofputil_phy_port;
struct simap;
struct sset;

/* ofproto supports two kinds of OpenFlow connections:
Expand Down Expand Up @@ -70,6 +71,8 @@ void connmgr_run(struct connmgr *,
struct ofpbuf *ofp_msg));
void connmgr_wait(struct connmgr *, bool handling_openflow);

void connmgr_get_memory_usage(const struct connmgr *, struct simap *usage);

struct ofproto *ofconn_get_ofproto(const struct ofconn *);

void connmgr_retry(struct connmgr *);
Expand Down
11 changes: 11 additions & 0 deletions ofproto/ofproto-dpif.c
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
#include "ofproto-dpif-governor.h"
#include "ofproto-dpif-sflow.h"
#include "poll-loop.h"
#include "simap.h"
#include "timer.h"
#include "unaligned.h"
#include "unixctl.h"
Expand Down Expand Up @@ -1056,6 +1057,15 @@ wait(struct ofproto *ofproto_)
}
}

static void
get_memory_usage(const struct ofproto *ofproto_, struct simap *usage)
{
const struct ofproto_dpif *ofproto = ofproto_dpif_cast(ofproto_);

simap_increase(usage, "facets", hmap_count(&ofproto->facets));
simap_increase(usage, "subfacets", hmap_count(&ofproto->subfacets));
}

static void
flush(struct ofproto *ofproto_)
{
Expand Down Expand Up @@ -7098,6 +7108,7 @@ const struct ofproto_class ofproto_dpif_class = {
run,
run_fast,
wait,
get_memory_usage,
flush,
get_features,
get_tables,
Expand Down
8 changes: 8 additions & 0 deletions ofproto/ofproto-provider.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
#include "timeval.h"

struct ofputil_flow_mod;
struct simap;

/* An OpenFlow switch.
*
Expand Down Expand Up @@ -396,6 +397,13 @@ struct ofproto_class {
* poll-loop.h. */
void (*wait)(struct ofproto *ofproto);

/* Adds some memory usage statistics for the implementation of 'ofproto'
* into 'usage', for use with memory_report().
*
* This function is optional. */
void (*get_memory_usage)(const struct ofproto *ofproto,
struct simap *usage);

/* Every "struct rule" in 'ofproto' is about to be deleted, one by one.
* This function may prepare for that, for example by clearing state in
* advance. It should *not* actually delete any "struct rule"s from
Expand Down
Loading

0 comments on commit 0d08568

Please sign in to comment.