📄 hibernate.c
字号:
/* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
* Copyright (c) 2007-2008, The Tor Project, Inc. */
/* See LICENSE for licensing information */
/* $Id$ */
const char hibernate_c_id[] =
"$Id$";
/**
* \file hibernate.c
* \brief Functions to close listeners, stop allowing new circuits,
* etc in preparation for closing down or going dormant; and to track
* bandwidth and time intervals to know when to hibernate and when to
* stop hibernating.
**/
/*
hibernating, phase 1:
- send destroy in response to create cells
- send end (policy failed) in response to begin cells
- close an OR conn when it has no circuits
hibernating, phase 2:
(entered when bandwidth hard limit reached)
- close all OR/AP/exit conns)
*/
#include "or.h"
/** Possible values of hibernate_state */
typedef enum {
/** We are running normally. */
HIBERNATE_STATE_LIVE=1,
/** We're trying to shut down cleanly, and we'll kill all active connections
* at shutdown_time. */
HIBERNATE_STATE_EXITING=2,
/** We're running low on allocated bandwidth for this period, so we won't
* accept any new connections. */
HIBERNATE_STATE_LOWBANDWIDTH=3,
/** We are hibernating, and we won't wake up till there's more bandwidth to
* use. */
HIBERNATE_STATE_DORMANT=4
} hibernate_state_t;
extern long stats_n_seconds_working; /* published uptime */
/** Are we currently awake, asleep, running out of bandwidth, or shutting
* down? */
static hibernate_state_t hibernate_state = HIBERNATE_STATE_LIVE;
/** If are hibernating, when do we plan to wake up? Set to 0 if we
* aren't hibernating. */
static time_t hibernate_end_time = 0;
/** If we are shutting down, when do we plan finally exit? Set to 0 if
* we aren't shutting down. */
static time_t shutdown_time = 0;
/** Possible accounting periods. */
typedef enum {
UNIT_MONTH=1, UNIT_WEEK=2, UNIT_DAY=3,
} time_unit_t;
/* Fields for accounting logic. Accounting overview:
*
* Accounting is designed to ensure that no more than N bytes are sent in
* either direction over a given interval (currently, one month, one week, or
* one day) We could
* try to do this by choking our bandwidth to a trickle, but that
* would make our streams useless. Instead, we estimate what our
* bandwidth usage will be, and guess how long we'll be able to
* provide that much bandwidth before hitting our limit. We then
* choose a random time within the accounting interval to come up (so
* that we don't get 50 Tors running on the 1st of the month and none
* on the 30th).
*
* Each interval runs as follows:
*
* 1. We guess our bandwidth usage, based on how much we used
* last time. We choose a "wakeup time" within the interval to come up.
* 2. Until the chosen wakeup time, we hibernate.
* 3. We come up at the wakeup time, and provide bandwidth until we are
* "very close" to running out.
* 4. Then we go into low-bandwidth mode, and stop accepting new
* connections, but provide bandwidth until we run out.
* 5. Then we hibernate until the end of the interval.
*
* If the interval ends before we run out of bandwidth, we go back to
* step one.
*/
/** How many bytes have we read in this accounting interval? */
static uint64_t n_bytes_read_in_interval = 0;
/** How many bytes have we written in this accounting interval? */
static uint64_t n_bytes_written_in_interval = 0;
/** How many seconds have we been running this interval? */
static uint32_t n_seconds_active_in_interval = 0;
/** When did this accounting interval start? */
static time_t interval_start_time = 0;
/** When will this accounting interval end? */
static time_t interval_end_time = 0;
/** How far into the accounting interval should we hibernate? */
static time_t interval_wakeup_time = 0;
/** How much bandwidth do we 'expect' to use per minute? (0 if we have no
* info from the last period.) */
static uint64_t expected_bandwidth_usage = 0;
/** What unit are we using for our accounting? */
static time_unit_t cfg_unit = UNIT_MONTH;
/** How many days,hours,minutes into each unit does our accounting interval
* start? */
static int cfg_start_day = 0;
static int cfg_start_hour = 0;
static int cfg_start_min = 0;
static void reset_accounting(time_t now);
static int read_bandwidth_usage(void);
static time_t start_of_accounting_period_after(time_t now);
static time_t start_of_accounting_period_containing(time_t now);
static void accounting_set_wakeup_time(void);
/* ************
* Functions for bandwidth accounting.
* ************/
/** Configure accounting start/end time settings based on
* options->AccountingStart. Return 0 on success, -1 on failure. If
* <b>validate_only</b> is true, do not change the current settings. */
int
accounting_parse_options(or_options_t *options, int validate_only)
{
time_unit_t unit;
int ok, idx;
long d,h,m;
smartlist_t *items;
const char *v = options->AccountingStart;
const char *s;
char *cp;
if (!v) {
if (!validate_only) {
cfg_unit = UNIT_MONTH;
cfg_start_day = 1;
cfg_start_hour = 0;
cfg_start_min = 0;
}
return 0;
}
items = smartlist_create();
smartlist_split_string(items, v, NULL,
SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK,0);
if (smartlist_len(items)<2) {
log_warn(LD_CONFIG, "Too few arguments to AccountingStart");
goto err;
}
s = smartlist_get(items,0);
if (0==strcasecmp(s, "month")) {
unit = UNIT_MONTH;
} else if (0==strcasecmp(s, "week")) {
unit = UNIT_WEEK;
} else if (0==strcasecmp(s, "day")) {
unit = UNIT_DAY;
} else {
log_warn(LD_CONFIG,
"Unrecognized accounting unit '%s': only 'month', 'week',"
" and 'day' are supported.", s);
goto err;
}
switch (unit) {
case UNIT_WEEK:
d = tor_parse_long(smartlist_get(items,1), 10, 1, 7, &ok, NULL);
if (!ok) {
log_warn(LD_CONFIG, "Weekly accounting must begin on a day between "
"1 (Monday) and 7 (Sunday)");
goto err;
}
break;
case UNIT_MONTH:
d = tor_parse_long(smartlist_get(items,1), 10, 1, 28, &ok, NULL);
if (!ok) {
log_warn(LD_CONFIG, "Monthly accounting must begin on a day between "
"1 and 28");
goto err;
}
break;
case UNIT_DAY:
d = 0;
break;
default:
tor_assert(0);
}
idx = unit==UNIT_DAY?1:2;
if (smartlist_len(items) != (idx+1)) {
log_warn(LD_CONFIG,"Accounting unit '%s' requires %d argument%s.",
s, idx, (idx>1)?"s":"");
goto err;
}
s = smartlist_get(items, idx);
h = tor_parse_long(s, 10, 0, 23, &ok, &cp);
if (!ok) {
log_warn(LD_CONFIG,"Accounting start time not parseable: bad hour.");
goto err;
}
if (!cp || *cp!=':') {
log_warn(LD_CONFIG,
"Accounting start time not parseable: not in HH:MM format");
goto err;
}
m = tor_parse_long(cp+1, 10, 0, 59, &ok, &cp);
if (!ok) {
log_warn(LD_CONFIG, "Accounting start time not parseable: bad minute");
goto err;
}
if (!cp || *cp!='\0') {
log_warn(LD_CONFIG,
"Accounting start time not parseable: not in HH:MM format");
goto err;
}
if (!validate_only) {
cfg_unit = unit;
cfg_start_day = (int)d;
cfg_start_hour = (int)h;
cfg_start_min = (int)m;
}
SMARTLIST_FOREACH(items, char *, item, tor_free(item));
smartlist_free(items);
return 0;
err:
SMARTLIST_FOREACH(items, char *, item, tor_free(item));
smartlist_free(items);
return -1;
}
/** If we want to manage the accounting system and potentially
* hibernate, return 1, else return 0.
*/
int
accounting_is_enabled(or_options_t *options)
{
if (options->AccountingMax)
return 1;
return 0;
}
/** Called from main.c to tell us that <b>seconds</b> seconds have
* passed, <b>n_read</b> bytes have been read, and <b>n_written</b>
* bytes have been written. */
void
accounting_add_bytes(size_t n_read, size_t n_written, int seconds)
{
n_bytes_read_in_interval += n_read;
n_bytes_written_in_interval += n_written;
/* If we haven't been called in 10 seconds, we're probably jumping
* around in time. */
n_seconds_active_in_interval += (seconds < 10) ? seconds : 0;
}
/** If get_end, return the end of the accounting period that contains
* the time <b>now</b>. Else, return the start of the accounting
* period that contains the time <b>now</b> */
static time_t
edge_of_accounting_period_containing(time_t now, int get_end)
{
int before;
struct tm tm;
tor_localtime_r(&now, &tm);
/* Set 'before' to true iff the current time is before the hh:mm
* changeover time for today. */
before = tm.tm_hour < cfg_start_hour ||
(tm.tm_hour == cfg_start_hour && tm.tm_min < cfg_start_min);
/* Dispatch by unit. First, find the start day of the given period;
* then, if get_end is true, increment to the end day. */
switch (cfg_unit)
{
case UNIT_MONTH: {
/* If this is before the Nth, we want the Nth of last month. */
if (tm.tm_mday < cfg_start_day ||
(tm.tm_mday < cfg_start_day && before)) {
--tm.tm_mon;
}
/* Otherwise, the month is correct. */
tm.tm_mday = cfg_start_day;
if (get_end)
++tm.tm_mon;
break;
}
case UNIT_WEEK: {
/* What is the 'target' day of the week in struct tm format? (We
say Sunday==7; struct tm says Sunday==0.) */
int wday = cfg_start_day % 7;
/* How many days do we subtract from today to get to the right day? */
int delta = (7+tm.tm_wday-wday)%7;
/* If we are on the right day, but the changeover hasn't happened yet,
* then subtract a whole week. */
if (delta == 0 && before)
delta = 7;
tm.tm_mday -= delta;
if (get_end)
tm.tm_mday += 7;
break;
}
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -