draft-ietf-ippm-reporting-00.txt | draft-ietf-ippm-reporting-01.txt | |||
---|---|---|---|---|
Network Working Group S. Shalunov | Network Working Group S. Shalunov | |||
Internet-Draft Internet2 | Internet-Draft Internet2 | |||
Expires: April 26, 2007 B. Lutzmann | ||||
F. Pouzols | ||||
October 23, 2006 | ||||
Reporting IP Performance Metrics to Users | Reporting IP Performance Metrics to Users | |||
draft-ietf-ippm-reporting-00.txt | draft-ietf-ippm-reporting-01.txt | |||
Status of this Memo | Status of this Memo | |||
By submitting this Internet-Draft, each author represents that any | By submitting this Internet-Draft, each author represents that any | |||
applicable patent or other IPR claims of which he or she is aware | applicable patent or other IPR claims of which he or she is aware | |||
have been or will be disclosed, and any of which he or she becomes | have been or will be disclosed, and any of which he or she becomes | |||
aware will be disclosed, in accordance with Section 6 of BCP 79. | aware will be disclosed, in accordance with Section 6 of BCP 79. | |||
Internet-Drafts are working documents of the Internet Engineering | Internet-Drafts are working documents of the Internet Engineering | |||
Task Force (IETF), its areas, and its working groups. Note that | Task Force (IETF), its areas, and its working groups. Note that | |||
skipping to change at page 1, line 31 | skipping to change at page 1, line 35 | |||
and may be updated, replaced, or obsoleted by other documents at any | and may be updated, replaced, or obsoleted by other documents at any | |||
time. It is inappropriate to use Internet-Drafts as reference | time. It is inappropriate to use Internet-Drafts as reference | |||
material or to cite them other than as "work in progress." | material or to cite them other than as "work in progress." | |||
The list of current Internet-Drafts can be accessed at | The list of current Internet-Drafts can be accessed at | |||
http://www.ietf.org/ietf/1id-abstracts.txt. | http://www.ietf.org/ietf/1id-abstracts.txt. | |||
The list of Internet-Draft Shadow Directories can be accessed at | The list of Internet-Draft Shadow Directories can be accessed at | |||
http://www.ietf.org/shadow.html. | http://www.ietf.org/shadow.html. | |||
This Internet-Draft will expire on October 3, 2006. | This Internet-Draft will expire on April 26, 2007. | |||
Copyright Notice | Copyright Notice | |||
Copyright (C) The Internet Society (2006). | Copyright (C) The Internet Society (2006). | |||
Abstract | Abstract | |||
The aim of this document is to define a small set of metrics that are | The aim of this document is to define a small set of metrics that are | |||
robust, easy to understand, orthogonal, relevant, and easy to | robust, easy to understand, orthogonal, relevant, and easy to | |||
compute. The IPPM WG has defined a large number of richly | compute. The IPPM WG has defined a large number of richly | |||
skipping to change at page 2, line 21 | skipping to change at page 2, line 21 | |||
4.1. Delay . . . . . . . . . . . . . . . . . . . . . . . . . . 7 | 4.1. Delay . . . . . . . . . . . . . . . . . . . . . . . . . . 7 | |||
4.2. Loss . . . . . . . . . . . . . . . . . . . . . . . . . . . 7 | 4.2. Loss . . . . . . . . . . . . . . . . . . . . . . . . . . . 7 | |||
4.3. Jitter . . . . . . . . . . . . . . . . . . . . . . . . . . 7 | 4.3. Jitter . . . . . . . . . . . . . . . . . . . . . . . . . . 7 | |||
4.4. Duplication . . . . . . . . . . . . . . . . . . . . . . . 8 | 4.4. Duplication . . . . . . . . . . . . . . . . . . . . . . . 8 | |||
4.5. Reordering . . . . . . . . . . . . . . . . . . . . . . . . 8 | 4.5. Reordering . . . . . . . . . . . . . . . . . . . . . . . . 8 | |||
5. Sample Source . . . . . . . . . . . . . . . . . . . . . . . . 9 | 5. Sample Source . . . . . . . . . . . . . . . . . . . . . . . . 9 | |||
5.1. One-Way Active Measurement . . . . . . . . . . . . . . . . 9 | 5.1. One-Way Active Measurement . . . . . . . . . . . . . . . . 9 | |||
5.2. Round-Trip Active Measurement . . . . . . . . . . . . . . 10 | 5.2. Round-Trip Active Measurement . . . . . . . . . . . . . . 10 | |||
5.3. Passive Measurement . . . . . . . . . . . . . . . . . . . 10 | 5.3. Passive Measurement . . . . . . . . . . . . . . . . . . . 10 | |||
6. Security Considerations . . . . . . . . . . . . . . . . . . . 11 | 6. Security Considerations . . . . . . . . . . . . . . . . . . . 11 | |||
7. Internationalization Considerations . . . . . . . . . . . . . 12 | 7. Acknowledgments . . . . . . . . . . . . . . . . . . . . . . . 12 | |||
8. IANA Considerations . . . . . . . . . . . . . . . . . . . . . 13 | 8. IANA Considerations . . . . . . . . . . . . . . . . . . . . . 13 | |||
9. Normative References . . . . . . . . . . . . . . . . . . . . . 13 | 9. Internationalization Considerations . . . . . . . . . . . . . 14 | |||
Appendix A. Acknowledgments . . . . . . . . . . . . . . . . . . . 14 | 10. Normative References . . . . . . . . . . . . . . . . . . . . . 14 | |||
Appendix B. TODO . . . . . . . . . . . . . . . . . . . . . . . . 15 | Appendix A. Sample Source Code for Computing the Metrics . . . . 15 | |||
Appendix C. Revision History . . . . . . . . . . . . . . . . . . 16 | Appendix B. Example Report . . . . . . . . . . . . . . . . . . . 39 | |||
Author's Address . . . . . . . . . . . . . . . . . . . . . . . . . 17 | Appendix C. TODO . . . . . . . . . . . . . . . . . . . . . . . . 40 | |||
Intellectual Property and Copyright Statements . . . . . . . . . . 18 | Appendix D. Revision History . . . . . . . . . . . . . . . . . . 41 | |||
Authors' Addresses . . . . . . . . . . . . . . . . . . . . . . . . 42 | ||||
Intellectual Property and Copyright Statements . . . . . . . . . . 43 | ||||
1. Requirements Notation | 1. Requirements Notation | |||
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", | The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", | |||
"SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this | "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this | |||
document are to be interpreted as described in [RFC2119]. | document are to be interpreted as described in [RFC2119]. | |||
2. Goals and Motivation | 2. Goals and Motivation | |||
The IPPM working group has defined many richly parameterized | The IPPM working group has defined many richly parameterized | |||
skipping to change at page 12, line 5 | skipping to change at page 12, line 5 | |||
An aspect of reporting relevant to security is how the reported | An aspect of reporting relevant to security is how the reported | |||
metrics are used and how they are collected. If it is important that | metrics are used and how they are collected. If it is important that | |||
the metrics satisfy certain conditions (e.g., that the ISP whose | the metrics satisfy certain conditions (e.g., that the ISP whose | |||
network is being measured be unable to make the metrics appear better | network is being measured be unable to make the metrics appear better | |||
than they are), the collection mechanism MUST ensure that this is, | than they are), the collection mechanism MUST ensure that this is, | |||
indeed, so. The exact mechanisms to do so our outside of scope of | indeed, so. The exact mechanisms to do so our outside of scope of | |||
this document and belong with discussion of particular measurement | this document and belong with discussion of particular measurement | |||
data collection protocols. | data collection protocols. | |||
7. Internationalization Considerations | 7. Acknowledgments | |||
The reported metrics, while they might occasionally be parsed by | We gratefully acknowledge discussion with, encouragement from, and | |||
machine, are primarily meant for human consumption. As such, they | contributions of Lawrence D. Dunn, Reza Fardid, Ruediger Geib, | |||
MAY be reported in the language preferred by the user, using an | Matt Mathis, Al Morton, Carsten Schmoll, Henk Uijterwaal, and | |||
encoding suitable for the purpose, such as UTF-8. | Matthew J. Zekauskas. | |||
8. IANA Considerations | 8. IANA Considerations | |||
This document requires no action from the IANA. | This document requires no action from the IANA. | |||
9. Normative References | 9. Internationalization Considerations | |||
The reported metrics, while they might occasionally be parsed by | ||||
machine, are primarily meant for human consumption. As such, they | ||||
MAY be reported in the language preferred by the user, using an | ||||
encoding suitable for the purpose, such as UTF-8. | ||||
10. Normative References | ||||
[RFC2119] Bradner, S., "Key words for use in RFCs to Indicate | [RFC2119] Bradner, S., "Key words for use in RFCs to Indicate | |||
Requirement Levels", BCP 14, RFC 2119, March 1997. | Requirement Levels", BCP 14, RFC 2119, March 1997. | |||
Appendix A. Acknowledgments | Appendix A. Sample Source Code for Computing the Metrics | |||
The author gratefully acknowledges discussion with, encouragement | This appendix only serves for illustrative purposes. | |||
from, and contributions of Lawrence D. Dunn, Reza Fardid, | ||||
Ruediger Geib, Matt Mathis, Al Morton, Carsten Schmoll, | ||||
Henk Uijterwaal, and Matthew J. Zekauskas. | ||||
Appendix B. TODO | /* | |||
* reporting.c -- performance metrics reporting as in Internet Draft | ||||
* draft-ietf-ippm-reporting. | ||||
* | ||||
* Written by Stanislav Shalunov, http://www.internet2.edu/~shalunov/ | ||||
* Bernhard Lutzmann, belu@users.sf.net | ||||
* Federico Montesino Pouzols, fedemp@altern.org | ||||
* | ||||
* This file is also available, under a different (BSD-style) | ||||
* license, as part of thrulay. | ||||
*/ | ||||
FIXME: This section needs to be removed before publication. | /** | |||
* @file reporting.c | ||||
* | ||||
* @short metrics computation and reporting. | ||||
**/ | ||||
o Add references | #include <stdlib.h> | |||
#include <stdint.h> | ||||
#include <float.h> | ||||
#include <math.h> | ||||
#include <string.h> | ||||
#include <assert.h> | ||||
o Add non-normative code for illustration | #define min(a, b) ((a) < (b) ? (a) : (b)) | |||
#define max(a, b) ((a) > (b) ? (a) : (b)) | ||||
o Add examples (code output) | /* | |||
* Reordering. | ||||
*/ | ||||
#define loop(x) ((x) >= 0 ? (x) : (x) + (int)reordering_max) | ||||
Appendix C. Revision History | /* | |||
* Duplication. | ||||
*/ | ||||
static uint64_t *bitfield = NULL; /* Bit field used to check for | ||||
duplicated packets. */ | ||||
/* | ||||
* Reordering. | ||||
*/ | ||||
static uint64_t reordering_max; /* We have m[j-1] == number of */ | ||||
static uint64_t *reordering_m; /* We have m[j-1] == number of | ||||
j-reordered packets. */ | ||||
static uint64_t *reordering_ring; /* Last sequence numbers seen */ | ||||
static int r = 0; /* Ring pointer for next write. */ | ||||
static int l = 0; /* Counter of sequence numbers. */ | ||||
/* | ||||
* Quantiles | ||||
* | ||||
* Reference: | ||||
* | ||||
* [1] Manku, Rajagopalan, Lindsay: "Approximate Medians and other | ||||
* Quantiles in One Pass and with Limited Memory", | ||||
* http://www-db.stanford.edu/~manku/papers/98sigmod-quantiles.pdf | ||||
*/ | ||||
#define QUANTILE_EPS 0.005 | ||||
static uint16_t quantile_max_seq; /* Maximum number of sequences */ | ||||
static int *quantile_k; /* number of elements in buffer */ | ||||
static double **quantile_input; /* This is the buffer where the | ||||
sequence of incoming packets is | ||||
saved. If we received enough | ||||
packets, we will write this | ||||
buffer to a quantile buffer. */ | ||||
static int *quantile_input_cnt; /* number of elements in input | ||||
* buffer */ | ||||
static int *quantile_empty_buffers; /* number of empty buffers */ | ||||
static int *quantile_b; /* number of buffers */ | ||||
static double **quantile_buf; | ||||
static int *quantile_alternate; /* this is used to determine | ||||
the offset in COLLAPSE (if | ||||
weight is even) */ | ||||
static uint64_t *quantile_inf_cnt; /* this counter is for the | ||||
additional -inf, +inf | ||||
elements we added to NEW | ||||
buffer to fill it up. */ | ||||
typedef struct quantile { | ||||
struct quantile *next; /* pointer to next quantile | ||||
* buffer */ | ||||
int weight; /* 0 if buffer is empty, > 0 if buffer is | ||||
* full */ | ||||
int level; | ||||
double *buffer; | ||||
int pos; /* current position in buffer; used in | ||||
quantile_collapse() */ | ||||
} quantile_t; | ||||
static quantile_t **quantile_buffer_head; | ||||
int | ||||
reordering_init(uint64_t max) | ||||
{ | ||||
reordering_max = max; | ||||
reordering_m = calloc(reordering_max, sizeof(uint64_t)); | ||||
reordering_ring = calloc(reordering_max, sizeof(uint64_t)); | ||||
if (reordering_m == NULL) { | ||||
return -1; | ||||
} else { | ||||
return 0; | ||||
} | ||||
} | ||||
int | ||||
reordering_checkin(uint64_t packet_sqn) | ||||
{ | ||||
int j; | ||||
for (j = 0; j < min(l, (int)reordering_max) && | ||||
packet_sqn < reordering_ring[loop(r-j-1)]; j++) { | ||||
reordering_m[j]++; | ||||
} | ||||
reordering_ring[r] = packet_sqn; | ||||
l++; | ||||
r = (r + 1) % reordering_max; | ||||
return 0; | ||||
} | ||||
double | ||||
reordering_output(uint64_t j) | ||||
{ | ||||
if (j >= reordering_max) | ||||
return -1; | ||||
else | ||||
return (double)reordering_m[j] / (l - j - 1); | ||||
} | ||||
void | ||||
reordering_exit(void) | ||||
{ | ||||
free(reordering_ring); | ||||
free(reordering_m); | ||||
} | ||||
int | ||||
duplication_init(uint64_t npackets) | ||||
{ | ||||
uint64_t bitfield_len = 0; /* Number of sectors in bitfield */ | ||||
/* Allocate memory for bit field */ | ||||
bitfield_len = ((npackets % 64)? | ||||
(npackets / 64 + 1) : npackets / 64); | ||||
bitfield = calloc(bitfield_len, sizeof(uint64_t)); | ||||
if (bitfield == NULL) { | ||||
return -1; | ||||
} else { | ||||
return 0; | ||||
} | ||||
} | ||||
int | ||||
duplication_check(uint64_t packet_sqn) | ||||
{ | ||||
uint64_t bitfield_sec; /* Which sector in bitfield */ | ||||
uint64_t bitfield_bit; /* Which bit in sector */ | ||||
/* Calculate sector */ | ||||
bitfield_sec = packet_sqn >> 6; | ||||
/* Calculate bit in sector */ | ||||
bitfield_bit = (uint64_t)1 << (packet_sqn & 63); | ||||
if (bitfield[bitfield_sec] & bitfield_bit) { | ||||
/* Duplicated packet */ | ||||
return 1; | ||||
} else { | ||||
/* Unique packet */ | ||||
bitfield[bitfield_sec] |= bitfield_bit; | ||||
return 0; | ||||
} | ||||
} | ||||
void | ||||
duplication_exit(void) | ||||
{ | ||||
free(bitfield); | ||||
} | ||||
/* Calculate binomial coefficient C(n, k). */ | ||||
int64_t | ||||
binomial (int n, int k) | ||||
{ | ||||
int64_t bc = 0; | ||||
int i, m; | ||||
/* C(n, k) = C(n, n-k) */ | ||||
k = min(k, n-k); | ||||
if (k >= 0) { | ||||
bc = 1; | ||||
m = max(k, n-k); | ||||
for (i = 1; i <= k; i++) { | ||||
bc = (bc * (m + i)) / i; | ||||
} | ||||
} | ||||
return bc; | ||||
} | ||||
int | ||||
quantile_compare(const void *d1, const void *d2) | ||||
{ | ||||
if (*(double *)d1 < *(double *)d2) { | ||||
return -1; | ||||
} else if (*(double *)d1 == *(double *)d2) { | ||||
return 0; | ||||
} else { | ||||
assert(*(double *)d1 > *(double *)d2); | ||||
return 1; | ||||
} | ||||
} | ||||
void | ||||
quantile_sort (double *list, int length) | ||||
{ | ||||
qsort(list, length, sizeof(double), quantile_compare); | ||||
} | ||||
/** | ||||
* Implementation of NEW operation from section 3.1 of paper [1]. | ||||
* | ||||
* Takes as input an empty buffer. Simply populates the quantile | ||||
* buffer with the next k elements from the input sequence, labels | ||||
* the buffer as full, and assigns it a weight of 1. | ||||
* | ||||
* If there are not enough elements to fill up the buffer, we | ||||
* alternately add -inf, +inf elements until buffer is full (-inf | ||||
* == 0, +inf == DBL_MAX). | ||||
* | ||||
* NOTE: We sort the elements in the input buffer before we copy | ||||
* them to the quantile buffer. | ||||
* | ||||
* @param seq Sequence index. | ||||
* | ||||
* @return | ||||
* @retval 0 on success. | ||||
* @retval -2 need an empty buffer. | ||||
* @retval -3 bad input sequence length. | ||||
**/ | ||||
int | ||||
quantile_new(uint16_t seq, quantile_t *q, double *input, int k, | ||||
int level) | ||||
{ | ||||
int i; | ||||
/* Check that buffer is really empty. */ | ||||
if (q->weight != 0) { | ||||
return -2; | ||||
} | ||||
/* Sanity check for length of input sequence. */ | ||||
if (k > quantile_k[seq]) { | ||||
return -3; | ||||
} | ||||
/* If there are not enough elements in the input buffer, fill | ||||
* it up with -inf, +inf elements. */ | ||||
for (i = k; i < quantile_k[seq]; i++) { | ||||
if (i % 2) { | ||||
input[i] = DBL_MAX; | ||||
} else { | ||||
input[i] = 0; | ||||
} | ||||
/* Increment counter that indicates how many additional | ||||
* elements we added to fill the buffer. */ | ||||
quantile_inf_cnt[seq]++; | ||||
} | ||||
quantile_sort(input, quantile_k[seq]); | ||||
memcpy(q->buffer, input, sizeof(double) * quantile_k[seq]); | ||||
/* Mark buffer as full and set level. */ | ||||
q->weight = 1; | ||||
q->level = level; | ||||
/* Update number of empty quantile buffers. */ | ||||
quantile_empty_buffers[seq]--; | ||||
return 0; | ||||
} | ||||
/* Implementation of COLLAPSE operation from section 3.2 of paper | ||||
* [1]. | ||||
* | ||||
* This is called from quantile_algorithm() if there are no empty | ||||
* buffers. We COLLAPSE all the full buffers, where level has | ||||
* value `level'. Output is written to the first buffer in linked | ||||
* list with level set to `level'. The level of the output buffer | ||||
* is increased by 1. All other buffers we used in the COLLAPSE | ||||
* are marked empty. */ | ||||
int | ||||
quantile_collapse(uint16_t seq, int level) | ||||
{ | ||||
quantile_t *qp = NULL, *qp_out = NULL; | ||||
int num_buffers = 0; /* number of buffers with level | ||||
* `level' */ | ||||
int weight = 0; /* weight of the output buffer */ | ||||
int offset; | ||||
int i, j; | ||||
double min_dbl; | ||||
long next_pos; | ||||
long merge_pos = 0; | ||||
/* Check that there are at least two full buffers with given | ||||
* level. Also calculate weight of output buffer. */ | ||||
for (qp = quantile_buffer_head[seq]; qp != NULL; qp = qp->next) { | ||||
if ((qp->weight != 0) && (qp->level == level)) { | ||||
num_buffers++; | ||||
weight += qp->weight; | ||||
qp->pos = 0; | ||||
} else { | ||||
/* We mark the buffers that are not used in this | ||||
* COLLAPSE. */ | ||||
qp->pos = -1; | ||||
} | ||||
} | ||||
if (num_buffers < 2) { | ||||
return -4; | ||||
} | ||||
/* NOTE: The elements in full buffers are sorted. So we don't | ||||
* have to do that again. | ||||
*/ | ||||
/* Search for first full buffer with matching level. This is | ||||
* the buffer where we save the output. */ | ||||
for (qp_out = quantile_buffer_head[seq]; qp_out != NULL; | ||||
qp_out = qp_out->next) { | ||||
if (qp_out->pos != -1) { | ||||
break; | ||||
} | ||||
} | ||||
/* Calculate offset */ | ||||
if (weight % 2) { | ||||
/* odd */ | ||||
offset = (weight + 1) / 2; | ||||
} else { | ||||
/* even - we alternate between two choices in each | ||||
* COLLAPSE */ | ||||
if (quantile_alternate[seq] % 2) { | ||||
offset = weight / 2; | ||||
} else { | ||||
offset = (weight + 2)/ 2; | ||||
} | ||||
quantile_alternate[seq] = (quantile_alternate[seq] + 1) % 2; | ||||
} | ||||
/* Initialize next position of element to save. Because first | ||||
* position is at 0, we have to decrement offset by 1. */ | ||||
next_pos = offset - 1; | ||||
for (i = 0; i < quantile_k[seq]; ) { | ||||
/* Search for current minimal element in all buffers. | ||||
* Because buffers are all sorted, we just have to check | ||||
* the element at current position. */ | ||||
min_dbl = DBL_MAX; | ||||
for (qp = quantile_buffer_head[seq]; qp != NULL; | ||||
qp = qp->next) { | ||||
/* Skip wrong buffers. */ | ||||
if (qp->pos == -1) { | ||||
continue; | ||||
} | ||||
/* Check that we are not at the end of buffer. */ | ||||
if (qp->pos >= quantile_k[seq]) { | ||||
continue; | ||||
} | ||||
/* Update minimum element. */ | ||||
min_dbl = min(min_dbl, qp->buffer[qp->pos]); | ||||
} | ||||
/* Now process this minimal element in all buffers. */ | ||||
for (qp = quantile_buffer_head[seq]; qp != NULL; | ||||
qp = qp->next) { | ||||
/* Skip wrong buffers. */ | ||||
if (qp->pos == -1) { | ||||
continue; | ||||
} | ||||
/* Now process minimal element in this buffer. */ | ||||
for (; (qp->buffer[qp->pos] == min_dbl) && | ||||
(qp->pos < quantile_k[seq]); | ||||
qp->pos++) { | ||||
/* We run this loop `qp->weight' times. | ||||
* We check there if we are in a position | ||||
* so we have to save this element in our | ||||
* output buffer. */ | ||||
for (j = 0; j < qp->weight; j++) { | ||||
if (next_pos == merge_pos) { | ||||
quantile_buf[seq][i] = min_dbl; | ||||
i++; | ||||
if (i == quantile_k[seq]) { | ||||
/* We have written | ||||
* all elements to | ||||
* output buffer, so | ||||
* exit global loop. */ | ||||
goto out; | ||||
} | ||||
/* Update next position. */ | ||||
next_pos += weight; | ||||
} | ||||
merge_pos++; | ||||
} /* for(j = 0; j < qp->weight; j++) */ | ||||
} /* for (; (qp->buffer[qp->pos] == min_dbl) && | ||||
(qp->pos < quantile_k[seq]); qp->pos++) */ | ||||
} /* for (qp = quantile_buffer_head[seq]; qp!=NULL; | ||||
qp = qp->next) */ | ||||
} /* for (i = 0; i < quantile_k[seq]; ) */ | ||||
out: | ||||
memcpy(qp_out->buffer, quantile_buf[seq], | ||||
sizeof(double) * quantile_k[seq]); | ||||
/* Update weight of output buffer. */ | ||||
qp_out->weight = weight; | ||||
qp_out->level = level+1; | ||||
/* Update list of empty buffers. */ | ||||
for (qp = quantile_buffer_head[seq]; qp != NULL; qp = qp->next) { | ||||
if ((qp->pos != -1) && (qp != qp_out)) { | ||||
qp->weight = 0; | ||||
qp->level = 0; | ||||
} | ||||
} | ||||
quantile_empty_buffers[seq] += num_buffers - 1; | ||||
return 0; | ||||
} | ||||
/** | ||||
* Implementation of COLLAPSE policies from section 3.4 of paper | ||||
* [1]. | ||||
* | ||||
* There are three different algorithms noted in the paper. We use | ||||
* the "New Algorithm". | ||||
* | ||||
* @param seq Sequence index. | ||||
* | ||||
* @return | ||||
* @retval 0 on success. | ||||
* @retval -1 quantiles not initialized. | ||||
* @retval -2 need an empty buffer for new operation. | ||||
* @retval -3 bad input sequence length in new operation. | ||||
* @retval -4 not enough buffers for collapse operation. | ||||
**/ | ||||
int | ||||
quantile_algorithm (uint16_t seq, double *input, int k) | ||||
{ | ||||
int rc; | ||||
quantile_t *qp = NULL, *qp2 = NULL; | ||||
int min_level = -1; | ||||
/* This should always be true. */ | ||||
if (quantile_buffer_head[seq] != NULL) { | ||||
min_level = quantile_buffer_head[seq]->level; | ||||
} else { | ||||
return -1; | ||||
} | ||||
/* Get minimum level of all currently full buffers. */ | ||||
for (qp = quantile_buffer_head[seq]; qp != NULL; qp = qp->next) { | ||||
if (qp->weight != 0) { | ||||
/* Full buffer. */ | ||||
min_level = min(min_level, qp->level); | ||||
} | ||||
} | ||||
if (quantile_empty_buffers[seq] == 0) { | ||||
/* There are no empty buffers. Invoke COLLAPSE on the set | ||||
* of buffers with minimum level. */ | ||||
rc = quantile_collapse(seq, min_level); | ||||
if (rc < 0) | ||||
return rc; | ||||
} else if (quantile_empty_buffers[seq] == 1) { | ||||
/* We have exactly one empty buffer. Invoke NEW and assign | ||||
* it level `min_level'. */ | ||||
/* Search the empty buffer. */ | ||||
for (qp = quantile_buffer_head[seq]; qp != NULL; | ||||
qp = qp->next) { | ||||
if (qp->weight == 0) { | ||||
/* Found empty buffer. */ | ||||
break; | ||||
} | ||||
} | ||||
rc = quantile_new(seq, qp, input, k, min_level); | ||||
if (rc < 0) | ||||
return rc; | ||||
} else { | ||||
/* There are at least two empty buffers. Invoke NEW on each | ||||
* and assign level `0' to each. */ | ||||
/* Search for two empty buffers. */ | ||||
for (qp = quantile_buffer_head[seq]; qp != NULL; | ||||
qp = qp->next) { | ||||
if (qp->weight == 0) { | ||||
/* Found first empty buffer. */ | ||||
break; | ||||
} | ||||
} | ||||
for (qp2 = qp->next; qp2 != NULL; qp2 = qp2->next) { | ||||
if (qp2->weight == 0) { | ||||
/* Found second empty buffer. */ | ||||
break; | ||||
} | ||||
} | ||||
if (k <= quantile_k[seq]) { | ||||
/* This could happen if we call this after we | ||||
* received all packets but don't have enough to | ||||
* fill up two buffers. */ | ||||
rc = quantile_new(seq, qp, input, k, 0); | ||||
if (rc < 0) | ||||
return rc; | ||||
} else { | ||||
/* We have enough input data for two buffers. */ | ||||
rc = quantile_new(seq, qp, input, quantile_k[seq], 0); | ||||
if (rc < 0) | ||||
return rc; | ||||
rc = quantile_new(seq, qp2, input + quantile_k[seq], | ||||
k - quantile_k[seq], 0); | ||||
if (rc < 0) | ||||
return rc; | ||||
} | ||||
} | ||||
return 0; | ||||
} | ||||
int | ||||
quantile_init_seq(uint16_t seq) | ||||
{ | ||||
quantile_t *qp = NULL; | ||||
int i; | ||||
if ( seq >= quantile_max_seq) | ||||
return -5; | ||||
/* Allocate memory for quantile buffers. Buffers are linked | ||||
* lists with a pointer to next buffer. We need `quantile_b' | ||||
* buffers, where each buffer has space for `quantile_k' | ||||
* elements. */ | ||||
for (i = 0; i < quantile_b[seq]; i++) { | ||||
if (i == 0) { | ||||
/* Initialize first buffer. */ | ||||
qp = malloc(sizeof(quantile_t)); | ||||
if (qp == NULL) { | ||||
return -1; | ||||
} | ||||
quantile_buffer_head[seq] = qp; | ||||
} else { | ||||
qp->next = malloc(sizeof(quantile_t)); | ||||
if (qp->next == NULL) { | ||||
return -1; | ||||
} | ||||
qp = qp->next; | ||||
} | ||||
/* `qp' points to buffer that should be initialized. */ | ||||
qp->next = NULL; | ||||
qp->weight = 0; /* empty buffers have weight of 0 */ | ||||
qp->level = 0; | ||||
qp->buffer = malloc(sizeof(double) * quantile_k[seq]); | ||||
if (qp->buffer == NULL) { | ||||
return -1; | ||||
} | ||||
} | ||||
/* Update number of empty quantile buffers. */ | ||||
quantile_empty_buffers[seq] = quantile_b[seq]; | ||||
return 0; | ||||
} | ||||
int | ||||
quantile_init (uint16_t max_seq, double eps, uint64_t N) | ||||
{ | ||||
int b, b_tmp = 0; | ||||
int k, k_tmp = 0; | ||||
int h, h_max = 0; | ||||
int seq, rc; | ||||
quantile_max_seq = max_seq; | ||||
/* Allocate array for the requested number of sequences. */ | ||||
quantile_k = calloc(max_seq, sizeof(int)); | ||||
quantile_input = calloc(max_seq, sizeof(double*)); | ||||
quantile_input_cnt = calloc(max_seq, sizeof(int)); | ||||
quantile_empty_buffers = calloc(max_seq, sizeof(int)); | ||||
quantile_b = calloc(max_seq, sizeof(int)); | ||||
quantile_buf = calloc(max_seq, sizeof(double*)); | ||||
quantile_alternate = calloc(max_seq, sizeof(int)); | ||||
quantile_inf_cnt = calloc(max_seq, sizeof(uint64_t)); | ||||
quantile_buffer_head = calloc(max_seq, sizeof(quantile_t*)); | ||||
/* "In practice, optimal values for b and k can be computed by | ||||
* trying out different values of b in the range 1 and 30." */ | ||||
for (b = 2; b <= 30; b++) { | ||||
/* For each b, compute the largest integral h that | ||||
* satisfies: | ||||
* (h-2) * C(b+h-2, h-1) - C(b+h-3, h-3) + | ||||
* C(b+h-3, h-2) <= 2 * eps * N | ||||
*/ | ||||
for (h = 0; ; h++) { | ||||
if (((h-2) * binomial(b+h-2, h-1) - | ||||
binomial(b+h-3, h-3) + | ||||
binomial(b+h-3, h-2)) > | ||||
(2 * eps * N)) { | ||||
/* This h does not satisfy the inequality from | ||||
* above. */ | ||||
break; | ||||
} | ||||
h_max = h; | ||||
} | ||||
/* Now compute the smallest integral k that satisfies: | ||||
* k * C(b+h-2, h-1) => N. */ | ||||
k = ceil(N / (double)binomial(b+h_max-2, h_max-1)); | ||||
/* Identify that b that minimizes b*k. */ | ||||
if ((b_tmp == 0) && (k_tmp == 0)) { | ||||
/* Initialize values */ | ||||
b_tmp = b; | ||||
k_tmp = k; | ||||
} | ||||
if ((b * k) < (b_tmp * k_tmp)) { | ||||
/* Found b and k for which the product is smaller than | ||||
* for the ones before. Because we want to minimize | ||||
* b*k (required memory), we save them. */ | ||||
b_tmp = b; | ||||
k_tmp = k; | ||||
} | ||||
} | ||||
/* Set global quantile values. For now, all sequences share | ||||
the same k and b values.*/ | ||||
for (seq = 0; seq < max_seq; seq++ ) { | ||||
quantile_b[seq] = b_tmp; | ||||
quantile_k[seq] = k_tmp; | ||||
} | ||||
/* Allocate memory for input buffer. We allocate enough space | ||||
* to save up to `2 * quantile_k' elements. This space is | ||||
* needed in the COLLAPSE policy if there are more than two | ||||
* empty buffers. Because then we have to invoke NEW on two | ||||
* buffers and thus need an input buffer with `2 * quantile_k' | ||||
* elements. */ | ||||
for (seq = 0; seq < quantile_max_seq; seq++) { | ||||
quantile_input[seq] = malloc(sizeof(double) * 2 * | ||||
quantile_k[seq]); | ||||
if (quantile_input[seq] == NULL) { | ||||
return -1; | ||||
} | ||||
quantile_input_cnt[seq] = 0; | ||||
} | ||||
/* Allocate memory for output buffer. This buffer is used in | ||||
* COLLAPSE to store temporary output buffer before it gets | ||||
* copied to one of the buffers used in COLLAPSE. */ | ||||
for (seq = 0; seq < quantile_max_seq; seq++ ) { | ||||
quantile_buf[seq] = malloc(sizeof(double) * quantile_k[seq]); | ||||
if (quantile_buf[seq] == NULL) { | ||||
return -1; | ||||
} | ||||
} | ||||
for (seq = 0; seq < max_seq; seq++) { | ||||
rc = quantile_init_seq(seq); | ||||
if (rc < 0) | ||||
return rc; | ||||
} | ||||
return 0; | ||||
} | ||||
int | ||||
quantile_value_checkin(uint16_t seq, double value) | ||||
{ | ||||
int rc = 0; | ||||
if ( seq >= quantile_max_seq) | ||||
return -5; | ||||
quantile_input[seq][quantile_input_cnt[seq]++] = value; | ||||
/* If we have at least two empty buffers, | ||||
* we need input for two buffers, to twice | ||||
* the value of `quantile_k'. */ | ||||
if (quantile_empty_buffers[seq] >= 2) { | ||||
if (quantile_input_cnt[seq] == | ||||
(2 * quantile_k[seq])) { | ||||
rc = quantile_algorithm(seq, quantile_input[seq], | ||||
quantile_input_cnt[seq]); | ||||
/* Reset counter. */ | ||||
quantile_input_cnt[seq] = 0; | ||||
} | ||||
} else { | ||||
/* There are 0 or 1 empty buffers */ | ||||
if (quantile_input_cnt[seq] == quantile_k[seq]) { | ||||
rc = quantile_algorithm(seq, quantile_input[seq], | ||||
quantile_input_cnt[seq]); | ||||
/* Reset counter. */ | ||||
quantile_input_cnt[seq] = 0; | ||||
} | ||||
} | ||||
return rc; | ||||
} | ||||
int | ||||
quantile_finish(uint16_t seq) | ||||
{ | ||||
int rc = 0; | ||||
if ( seq >= quantile_max_seq) | ||||
return -5; | ||||
if (quantile_input_cnt[seq] > 0) { | ||||
rc = quantile_algorithm(seq, quantile_input[seq], | ||||
quantile_input_cnt[seq]); | ||||
} | ||||
return rc; | ||||
} | ||||
void | ||||
quantile_reset(uint16_t seq) | ||||
{ | ||||
quantile_input_cnt[seq] = 0; | ||||
quantile_empty_buffers[seq] = quantile_b[seq]; | ||||
memset(quantile_buf[seq],0,sizeof(double) * quantile_k[seq]); | ||||
memset(quantile_input[seq],0,sizeof(double) * quantile_k[seq]); | ||||
} | ||||
/** | ||||
* Deinitialize one quantile sequence. | ||||
**/ | ||||
void | ||||
quantile_exit_seq(uint16_t seq) | ||||
{ | ||||
quantile_t *qp = NULL, *next; | ||||
if (seq >= quantile_max_seq) | ||||
return; | ||||
qp = quantile_buffer_head[seq]; | ||||
while (qp != NULL) { | ||||
/* Save pointer to next buffer. */ | ||||
next = qp->next; | ||||
/* Free buffer and list entry. */ | ||||
free(qp->buffer); | ||||
free(qp); | ||||
/* Set current buffer to next one. */ | ||||
qp = next; | ||||
} | ||||
quantile_buffer_head[seq] = NULL; | ||||
quantile_input_cnt[seq] = 0; | ||||
quantile_empty_buffers[seq] = quantile_b[seq]; | ||||
} | ||||
void | ||||
quantile_exit(void) | ||||
{ | ||||
int seq; | ||||
/* Free per sequence structures */ | ||||
for (seq = 0; seq < quantile_max_seq; seq++) { | ||||
quantile_exit_seq(seq); | ||||
/* Free output buffer. */ | ||||
free(quantile_buf[seq]); | ||||
/* Free input buffer. */ | ||||
free(quantile_input[seq]); | ||||
} | ||||
free(quantile_buffer_head); | ||||
free(quantile_inf_cnt); | ||||
free(quantile_alternate); | ||||
free(quantile_buf); | ||||
free(quantile_b); | ||||
free(quantile_empty_buffers); | ||||
free(quantile_input_cnt); | ||||
free(quantile_input); | ||||
free(quantile_k); | ||||
} | ||||
int | ||||
quantile_output (uint16_t seq, uint64_t npackets, double phi, | ||||
double *result) | ||||
{ | ||||
quantile_t *qp = NULL; | ||||
int num_buffers = 0; | ||||
int weight = 0; | ||||
int j; | ||||
long next_pos = 0; | ||||
long merge_pos = 0; | ||||
double min_dbl; | ||||
double beta; | ||||
double phi2; /* this is phi' */ | ||||
if ( seq >= quantile_max_seq) | ||||
return -5; | ||||
/* Check that there are at least two full buffers with given | ||||
* level. */ | ||||
for (qp = quantile_buffer_head[seq]; qp != NULL; qp = qp->next) { | ||||
if (qp->weight != 0) { | ||||
num_buffers++; | ||||
weight += qp->weight; | ||||
qp->pos = 0; | ||||
} else { | ||||
qp->pos = -1; | ||||
} | ||||
} | ||||
if (num_buffers < 2) { | ||||
/* XXX: In section 3.3 "OUTPUT operation" of paper [1] is | ||||
* says that OUTPUT takes c => 2 full input buffers. But | ||||
* what if we just have one full input buffer? | ||||
* | ||||
* For example this happens if you run a UDP test with a | ||||
* block size of 100k and a test duration of 3 seconds: $ | ||||
* ./thrulay -u 100k -t 3 localhost | ||||
*/ | ||||
if (num_buffers != 1) { | ||||
return -1; | ||||
} | ||||
} | ||||
/* Calculate beta and phi' */ | ||||
beta = 1 + quantile_inf_cnt[seq] / (double)npackets; | ||||
assert(beta >= 1.0); | ||||
assert(phi >= 0.0 && phi <= 1.0); | ||||
phi2 = (2 * phi + beta - 1) / (2 * beta); | ||||
next_pos = ceil(phi2 * quantile_k[seq] * weight); | ||||
/* XXX: If the client just sends a few packets, it is possible | ||||
* that next_pos is too large. If this is the case, decrease | ||||
* it. */ | ||||
if (next_pos >= (num_buffers * quantile_k[seq])) { | ||||
next_pos --; | ||||
} | ||||
while (1) { | ||||
/* Search for current minimal element in all buffers. | ||||
* Because buffers are all sorted, we just have to check | ||||
* the element at current position. */ | ||||
min_dbl = DBL_MAX; | ||||
for (qp = quantile_buffer_head[seq]; qp != NULL; | ||||
qp = qp->next) { | ||||
/* Skip wrong buffers. */ | ||||
if (qp->pos == -1) { | ||||
continue; | ||||
} | ||||
/* Check that we are not at the end of buffer. */ | ||||
if (qp->pos >= quantile_k[seq]) { | ||||
continue; | ||||
} | ||||
/* Update minimum element. */ | ||||
min_dbl = min(min_dbl, qp->buffer[qp->pos]); | ||||
} | ||||
/* Now process this minimal element in all buffers. */ | ||||
for (qp = quantile_buffer_head[seq]; qp != NULL; | ||||
qp = qp->next) { | ||||
/* Skip wrong buffers. */ | ||||
if (qp->pos == -1) { | ||||
continue; | ||||
} | ||||
/* Now process minimal element in this buffer. */ | ||||
for (; (qp->buffer[qp->pos] == min_dbl) && | ||||
(qp->pos < quantile_k[seq]); | ||||
qp->pos++) { | ||||
/* Increment merge position `qp->weight' | ||||
* times. If we pass the position we seek, | ||||
* return current minimal element. */ | ||||
for (j = 0; j < qp->weight; j++) { | ||||
if (next_pos == merge_pos) { | ||||
*result = min_dbl; | ||||
return 0; | ||||
} | ||||
merge_pos++; | ||||
} | ||||
} | ||||
} | ||||
} | ||||
/* NOTREACHED */ | ||||
} | ||||
#ifdef THRULAY_REPORTING_SAMPLE_LOOP | ||||
#include <stdio.h> | ||||
#include <strings.h> | ||||
#ifndef NAN | ||||
#define _ISOC99_SOURCE | ||||
#include <math.h> | ||||
#endif | ||||
#define ERR_FATAL 0 | ||||
#define ERR_WARNING 1 | ||||
void __attribute__((noreturn)) | ||||
quantile_alg_error(int rc) | ||||
{ | ||||
switch (rc) { | ||||
case -1: | ||||
fprintf(stderr, "Error: quantiles not initialized."); | ||||
break; | ||||
case -2: | ||||
fprintf(stderr, "Error: NEW needs an empty buffer."); | ||||
break; | ||||
case -3: | ||||
fprintf(stderr, "Error: Bad input sequence length."); | ||||
break; | ||||
case -4: | ||||
fprintf(stderr, "Error: Not enough buffers for COLLAPSE."); | ||||
break; | ||||
default: | ||||
fprintf(stderr, "Error: Unknown quantile_algorithm error."); | ||||
} | ||||
exit(1); | ||||
} | ||||
/** | ||||
* Will read a sample data file (first and only parameter) whose | ||||
* lines give two values per line (per received packet): measured | ||||
* packet delay and packet sequence number (in "%lf %lu" | ||||
* format). As an exception, the first line specifies the number | ||||
* of packets actually sent. Example: | ||||
* ---- | ||||
10 | ||||
0.101 0 | ||||
0.109 1 | ||||
0.12 1 | ||||
0.10 3 | ||||
0.14 4 | ||||
0.15 5 | ||||
0.13 2 | ||||
0.09 6 | ||||
0.1 8 | ||||
0.091 7 | ||||
* ---- | ||||
* | ||||
* To compile this sample reporting main(): | ||||
* | ||||
* gcc -std=c99 -DTHRULAY_REPORTING_SAMPLE_LOOP reporting.c -lm | ||||
* | ||||
**/ | ||||
int | ||||
main(int argc, char *argv[]) | ||||
{ | ||||
FILE *sf; | ||||
/* 'Measured data' */ | ||||
const int max_packets = 65535; | ||||
/* 'Received' packets*/ | ||||
int npackets = 0; | ||||
uint64_t packet_sqn[max_packets]; /* Fill in with sample data */ | ||||
double packet_delay[max_packets]; /* Fill in with sample data */ | ||||
uint64_t packets_sent = 0; /* Fill in with sample data */ | ||||
/* reordering */ | ||||
const uint64_t reordering_max = 100; | ||||
char buffer_reord[reordering_max * 80]; | ||||
size_t r = 0; | ||||
uint64_t j = 0; | ||||
/* Stats */ | ||||
uint64_t unique_packets = 0, packets_dup = 0; | ||||
double quantile_25, quantile_50, quantile_75; | ||||
double delay, jitter; | ||||
double packet_loss; | ||||
char report_buffer[1000]; | ||||
/* Auxiliary variables */ | ||||
int i, rc, rc2, rc3; | ||||
memset(packet_sqn,0,sizeof(uint64_t)*max_packets); | ||||
memset(packet_delay,0,sizeof(double)*max_packets); | ||||
/* Inititalize duplication */ | ||||
rc = duplication_init(max_packets); | ||||
if (-1 == rc) { | ||||
perror("calloc"); | ||||
exit(1); | ||||
} | ||||
/* Initialize quantiles */ | ||||
rc = quantile_init(1, QUANTILE_EPS, max_packets); | ||||
if (-1 == rc) { | ||||
perror("malloc"); | ||||
exit(1); | ||||
} | ||||
/* Initialize reordering */ | ||||
rc = reordering_init(reordering_max); | ||||
if (-1 == rc) { | ||||
perror("calloc"); | ||||
exit(1); | ||||
} | ||||
/* Open sample file */ | ||||
if (2 == argc) { | ||||
sf = fopen(argv[1],"r"); | ||||
} else { | ||||
fprintf(stderr, "no input file\n"); | ||||
exit(1); | ||||
} | ||||
/* Process sample input file. */ | ||||
/* The sender somehow tells the receiver how many packets were | ||||
actually sent. */ | ||||
fscanf(sf,"%lu",&packets_sent); | ||||
for (i = 0; i < max_packets && !feof(sf); i++) { | ||||
fscanf(sf,"%lf %lu",&packet_delay[i],&packet_sqn[i]); | ||||
npackets++; | ||||
/* | ||||
* Duplication | ||||
*/ | ||||
if (duplication_check(packet_sqn[i])) { | ||||
/* Duplicated packet */ | ||||
packets_dup++; | ||||
continue; | ||||
} else { | ||||
/* Unique packet */ | ||||
unique_packets++; | ||||
} | ||||
/* | ||||
* Delay quantiles. | ||||
*/ | ||||
rc = quantile_value_checkin(0, packet_delay[i]); | ||||
if (rc < 0) | ||||
quantile_alg_error(rc); | ||||
/* | ||||
* Reordering | ||||
*/ | ||||
reordering_checkin(packet_sqn[i]); | ||||
} | ||||
/* | ||||
* Perform last algorithm operation with a possibly not full | ||||
* input buffer. | ||||
*/ | ||||
rc = quantile_finish(0); | ||||
if (rc < 0) | ||||
quantile_alg_error(rc); | ||||
rc = quantile_output(0, unique_packets, 0.25, &quantile_25); | ||||
rc2 = quantile_output(0, unique_packets, 0.50, &quantile_50); | ||||
rc3 = quantile_output(0, unique_packets, 0.75, &quantile_75); | ||||
if (-1 == rc || -1 == rc2 || -1 == rc3) { | ||||
fprintf(stderr,"An error occurred while computing delay " | ||||
"quantiles. %d %d %d\n",rc, rc2, rc3); | ||||
exit(1); | ||||
} | ||||
/* Delay and jitter computation */ | ||||
packet_loss = packets_sent > unique_packets? | ||||
(100.0*(packets_sent - unique_packets))/packets_sent: 0; | ||||
delay = (packet_loss > 50.0)? INFINITY : quantile_50; | ||||
if (packet_loss < 25.0 ) { | ||||
jitter = quantile_75 - quantile_25; | ||||
} else if (packet_loss > 75.0) { | ||||
jitter = NAN; | ||||
} else { | ||||
jitter = INFINITY; | ||||
} | ||||
/* Format final report */ | ||||
snprintf(report_buffer, sizeof(report_buffer), | ||||
"Delay: %3.3fms\n" | ||||
"Loss: %3.3f%%\n" | ||||
"Jitter: %3.3fms\n" | ||||
"Duplication: %3.3f%%\n" | ||||
"Reordering: %3.3f%%\n", | ||||
1000.0 * delay, | ||||
packet_loss, | ||||
1000.0 * jitter, | ||||
100 * (double)packets_dup/npackets, | ||||
100.0 * reordering_output(0)); | ||||
printf("%s", report_buffer); | ||||
/* Deallocate resources for statistics. */ | ||||
reordering_exit(); | ||||
quantile_exit(); | ||||
duplication_exit(); | ||||
fclose(sf); | ||||
exit(0); | ||||
} | ||||
#endif /* THRULAY_REPORTING_SAMPLE_LOOP */ | ||||
Appendix B. Example Report | ||||
This appendix only serves for illustrative purposes. | ||||
This report is produced by running the sample program in Appendix A | ||||
on the sample input embedded in a comment in its source code: | ||||
Delay: 109.000ms | ||||
Loss: 10.000% | ||||
Jitter: 40.000ms | ||||
Duplication: 18.182% | ||||
Reordering: 25.000% | ||||
Appendix C. TODO | ||||
FIXME: This section needs to be removed before publication. | ||||
o Add references | ||||
Appendix D. Revision History | ||||
FIXME: This section needs to be removed before publication. | FIXME: This section needs to be removed before publication. | |||
$Log: draft-ietf-ippm-reporting.xml,v $ | $Log: draft-ietf-ippm-reporting.xml,v $ | |||
Revision 1.8 2006/10/23 21:45:54 shalunov | ||||
draft-ietf-ippm-reporting-01.txt | ||||
Revision 1.7 2006/10/23 21:45:13 shalunov | ||||
Add sample source code and output. | ||||
Revision 1.6 2006/06/02 21:21:57 shalunov | Revision 1.6 2006/06/02 21:21:57 shalunov | |||
draft-ietf-ippm-reporting-00: Include a ``Scope'' section. | draft-ietf-ippm-reporting-00: Include a ``Scope'' section. | |||
Change tags from draft-shalunov-ippm-reporting. | Change tags from draft-shalunov-ippm-reporting. | |||
Revision 1.5 2006/05/02 20:25:44 shalunov | Revision 1.5 2006/05/02 20:25:44 shalunov | |||
Version 03: Various refinements and clarifications based on feedback | Version 03: Various refinements and clarifications based on feedback | |||
from Reza Fardid, Ruediger Geib, and Al Morton. | from Reza Fardid, Ruediger Geib, and Al Morton. | |||
Revision 1.4 2006/04/25 22:38:58 shalunov | Revision 1.4 2006/04/25 22:38:58 shalunov | |||
Version 02: Address comments from Carsten Schmoll, sent in message | Version 02: Address comments from Carsten Schmoll, sent in message | |||
skipping to change at page 17, line 5 | skipping to change at page 42, line 5 | |||
Revision 1.3 2006/04/11 22:09:47 shalunov | Revision 1.3 2006/04/11 22:09:47 shalunov | |||
Version 01: Wording changes based on discussion with Matt Zekauskas | Version 01: Wording changes based on discussion with Matt Zekauskas | |||
(reordering, loss). Rewrite abstract a bit. Add TODO list. | (reordering, loss). Rewrite abstract a bit. Add TODO list. | |||
Revision 1.2 2006/04/04 21:39:20 shalunov | Revision 1.2 2006/04/04 21:39:20 shalunov | |||
Convert to xml2rfc 1.30 and RFC 3978 IPR statement. | Convert to xml2rfc 1.30 and RFC 3978 IPR statement. | |||
Revision 1.1.1.1 2006/04/02 17:07:36 shalunov | Revision 1.1.1.1 2006/04/02 17:07:36 shalunov | |||
Initial import into CVS. | Initial import into CVS. | |||
Author's Address | Authors' Addresses | |||
Stanislav Shalunov | Stanislav Shalunov | |||
Internet2 | Internet2 | |||
1000 Oakbrook Drive, Suite 300 | 1000 Oakbrook Drive, Suite 300 | |||
Ann Arbor, MI 48104 | Ann Arbor, MI 48104 | |||
US | US | |||
Phone: +1-734-913-4260 | ||||
Email: shalunov@internet2.edu | Email: shalunov@internet2.edu | |||
URI: http://www.internet2.edu/~shalunov/ | URI: http://www.internet2.edu/~shalunov/ | |||
Bernhard Lutzmann | ||||
Email: belu@users.sf.net | ||||
Federico Montesino Pouzols | ||||
Email: fedemp@altern.org | ||||
Intellectual Property Statement | Intellectual Property Statement | |||
The IETF takes no position regarding the validity or scope of any | The IETF takes no position regarding the validity or scope of any | |||
Intellectual Property Rights or other rights that might be claimed to | Intellectual Property Rights or other rights that might be claimed to | |||
pertain to the implementation or use of the technology described in | pertain to the implementation or use of the technology described in | |||
this document or the extent to which any license under such rights | this document or the extent to which any license under such rights | |||
might or might not be available; nor does it represent that it has | might or might not be available; nor does it represent that it has | |||
made any independent effort to identify any such rights. Information | made any independent effort to identify any such rights. Information | |||
on the procedures with respect to rights in RFC documents can be | on the procedures with respect to rights in RFC documents can be | |||
found in BCP 78 and BCP 79. | found in BCP 78 and BCP 79. | |||
End of changes. 20 change blocks. | ||||
28 lines changed or deleted | 1183 lines changed or added | |||
This html diff was produced by rfcdiff 1.33. The latest version is available from http://tools.ietf.org/tools/rfcdiff/ |