Commit ba937310 authored by Andreas Schmidt's avatar Andreas Schmidt

Add xlap timestamping code and evaluation suite.

parent ba753077
build/
dist/
xlap.egg-info/
.ipynb_checkpoints/
\ No newline at end of file
# X-Lap: A Systems Approach for Cross-Layer Profiling and Latency Analysis for Cyber-Physical Networks
## How to install X-Lap?
Enter the project directory and type `python setup.py install` to install `xlap` on your system.
## How to use X-lap?
### Step 0: Define your relevant timestamps across a packet trace.
Define time- and cyclestamps:
* Where data enters the sender app.
* Where data leaves the sender app.
* Where data enters the receiver app.
* Where data leaves the receiver app.
* Define additional cyclestamps wherever appropriate.
* Define interesting durations inside xlap.yml.
### Step 1: Instrument your code and generate traces
* Define a Sender and Receiver Application (send arbitrary data around).
* Initialize the xlap facility.
* Add stamping code where-ever appropriate.
* There are different mechanisms for adding timestamps.
### Step 2: Use the provided xlap analysis tools
* Write your `xlap.yml` file as follows:
```yml
data_files:
sender: sender.csv
receiver: receiver.csv
cycle_reference:
sender: StampA
receiver: SampB
time_reference:
sender: StampC
receiver: StampE
stamps:
StampA:
Source: receiver
Type: time
StampB:
Source: receiver
Type: time
StampC:
Source: receiver
Type: time
StampD:
Source: receiver
Type: cycle
StampE:
Source: receiver
Type: time
durations:
DurationA:
Start: StampA
Stop: StampB
Source: sender
DurationB:
Start: StampC
Stop: StampD
Source: receiver
packet_types:
Data: 0
Ack: 1
```
* You can run a jitter analysis via `xlap jitter`. The parameter `-e` exports the diagrams as `pdf`.
* The best way to interact with `xlap` is through a [Jupyter Python 3 Notebook](http://jupyter.org/). An example can be found in `notebook.ipynb`.
* `xlap.analyse.trace.traces()` can be used to investigate individual packet traces.
## Conventions
* Suffixes (indicate type):
* `_T`: Timestamp (us precision)
* `_C`: Clockstamp
* `_D`: Duration (us precision)
* Reserved Names (including suffixes as mentioned before):
* `Channel`
* `Sender`
* `SenderCycle`
* `Receiver`
* `ReceiverCycle`
* `EndToEnd`
* Note that expressions with other suffixes such as `SenderQueueing_T` would be allowed.
This source diff could not be displayed because it is too large. You can view the blob instead.
This diff is collapsed.
numpy==1.12.1
scipy==0.19.0
pandas==0.20.1
matplotlib==2.0.1
sklearn==0.0
ruamel.yaml==3.12
from setuptools import setup, find_packages
setup(
name="xlap",
version="0.0.2",
packages=find_packages(),
entry_points={
"console_scripts": [
"xlap=xlap.command_line:main"
]
}
)
python setup.py install
#define XLAP
#include "xlap.h"
#include <stdatomic.h>
void XlapTimestampTableDumpHeader(FILE *out)
{
fprintf(out, "SeqNo,Kind" );
# define OUT(id) fprintf(out, ",%s_T,%s_C", #id, #id);
PP_foreach(PP_join_space, OUT, TIMESTAMP_ID_LIST)
# undef OUT
fprintf(out, "\n");
}
static inline unsigned long long timestampByTime(struct timespec *ts)
{
// convert timespec to microseconds
unsigned long long x = ts->tv_sec;
x *= 1000000ULL;
x += ts->tv_nsec / 1000;
return x;
}
void XlapTimestampTableDump(FILE *out, XlapTimestampPacketKind kind, XlapTimestampTable *table)
{
# define OUT(id) fprintf(out, ",%llu,%llu", timestampByTime(&table->rows[row].time[ts_##id].actual.t), (unsigned long long) table->rows[row].time[ts_##id].actual.c);
for (unsigned int row = 0; row < TS_ROWS; row++) {
fprintf(out, "%u,%u", row, (unsigned) kind);
PP_foreach(PP_join_space, OUT, TIMESTAMP_ID_LIST)
fprintf(out, "\n");
}
# undef OUT
}
#ifndef XLAP_H
#define XLAP_H
#include <stdio.h>
#include <stdint.h>
#include <time.h>
/*
* Each timestamp contains both a cycle value and a time value, but some values
* might remain zero
*/
typedef union XlapTimestamp {
struct XlapActualTimestamp {
uint64_t c;
struct timespec t;
} actual;
char _cacheline[64];
} XlapTimestamp;
#include "pp.h"
/*
* Comma-separated list of timestamp IDs
*/
#define TIMESTAMP_ID_LIST \
PrrtSendStart, \
PrrtSendEnd, \
PrrtSubmitPackage, \
PrrtEncodeStart, \
PrrtEncodeEnd, \
PrrtTransmitStart, \
PrrtTransmitEnd, \
LinkTransmitStart, \
LinkTransmitEnd, \
LinkReceive, \
DecodeStart, \
DecodeEnd, \
HandlePacketStart, \
HandlePacketEnd, \
CopyOutputStart, \
CopyOutputEnd, \
SendFeedbackStart, \
SendFeedbackEnd, \
PrrtReturnPackage, \
PrrtReceivePackage, \
PrrtDeliver \
#define TIMESSTAMP_ID_TO_NAME(id) ts_##id
/*
* enum containing all timestamp IDs
*/
typedef enum XlapTimestampId {
PP_foreach(PP_join_comma, TIMESSTAMP_ID_TO_NAME, TIMESTAMP_ID_LIST),
ts_count
} XlapTimestampId;
/*
* enum to distinguish between data and redundancy packet timstamps
*/
typedef enum XlapTimestampPacketKind {
ts_data_packet = 0,
ts_any_packet = 0,
ts_redundancy_packet = 1,
} XlapTimestampPacketKind;
/*
* Table that stores timestamps for each timestamp ID
*/
typedef struct XlapTimestampTableRow {
XlapTimestamp time[ts_count];
} XlapTimestampTableRow;
/*
* by default, store timestamps for 128 packages
*/
#ifndef TS_ROWS
#define TS_ROWS (1u<<12)
#endif
/*
* Table that stores timestamp table rows
*/
#ifdef XLAP
typedef struct XlapTimestampTable {
XlapTimestampTableRow rows[TS_ROWS];
} XlapTimestampTable;
#else /* XLAP */
typedef char XlapTimestampTable;
#endif
/*
* Dummy data structure to store a single timestamp table row.
*/
#ifdef XLAP
typedef struct XlapTimestampPlaceholder {
_Atomic(XlapTimestampTable *) tstable[1];
XlapTimestampTableRow rows[1];
} XlapTimestampPlaceholder;
#else /* XLAP */
typedef char XlapTimestampPlaceholder;
#endif
/*
* update the clock value of a timestamp
*
* This macro will cause a SIGSEGV if the application does not install a
* timestamp table to the socket.
*/
#ifdef XLAP
# define XlapTimeStampClock(sck, kind, seqno, id) do { \
clock_gettime(CLOCK_MONOTONIC, &atomic_load_explicit(&(sck)->tstable[kind], memory_order_acquire)->rows[(seqno) % TS_ROWS].time[ts_##id].actual.t); \
} while (0)
#else /* XLAP */
# define XlapTimeStampClock(sck, kind, seqno, id) do { \
(void) (sck); \
(void) (kind); \
(void) (seqno); \
(void) (ts_##id); \
} while (0)
#endif
/*
* update the cycle value of a timestamp
*
* This macro will cause a SIGSEGV if the application does not install a
* timestamp table to the socket.
*/
#ifdef XLAP
# define XlapTimeStampCycle(sck, kind, seqno, id) do { \
atomic_load_explicit(&(sck)->tstable[kind], memory_order_acquire)->rows[(seqno) % TS_ROWS].time[ts_##id].actual.c = __builtin_ia32_rdtsc(); \
} while (0)
#else /* XLAP */
# define XlapTimeStampCycle(sck, kind, seqno, id) do { \
(void) (sck); \
(void) (kind); \
(void) (seqno); \
(void) (ts_##id); \
} while (0)
#endif
/*
* install a time stamp table to a socket
*/
#ifdef XLAP
# define XlapTimestampTableInstall(sck, kind, tstp) do { \
XlapTimestampTable *__tstp = (tstp); \
memset(__tstp, 0, sizeof(XlapTimestampTable)); \
atomic_store_explicit(&(sck)->tstable[kind], __tstp, memory_order_release); \
} while (0)
#else /* XLAP */
# define XlapTimestampTableInstall(sck, kind, tstp) do { \
(void) (sck); \
(void) (kind); \
(void) (tstp); \
} while (0)
#endif
/*
* print a timestamp dump header
*/
#ifdef XLAP
extern void XlapTimestampTableDumpHeader(FILE *);
#else
# define XlapTimestampTableDumpHeader(f) do { \
(void) (f); \
} while (0)
#endif
/*
* dump a timestamp table
*/
#ifdef XLAP
extern void XlapTimestampTableDump(FILE *, XlapTimestampPacketKind, XlapTimestampTable *);
#else
# define XlapTimestampTableDump(f, k, t) do { \
(void) (f); \
(void) (k); \
(void) (t); \
} while (0)
#endif
/*
* intialize a timestamp table placeholder
*/
#ifdef XLAP
# define XlapTimestampPlaceholderInitialize(ph) do { \
XlapTimestampPlaceholder *__ph = (ph); \
atomic_store_explicit(&__ph->tstable[0], (XlapTimestampTable *) &__ph->rows, memory_order_release); \
memset(&__ph->rows, 0x0, sizeof(XlapTimestampTableRow)); \
} while (0)
#else
# define XlapTimestampPlaceholderInitialize(ph) do { \
(void) (ph); \
} while (0)
#endif
/*
* copy a timestamp table placeholder into an actual timestamp table
*
* Since every timestamp is taken at most once, either the timestamptable or
* the placeholder value must be zero (for each timestamp).
*/
#ifdef XLAP
# define XlapTimestampPlaceholderUse(sck, kind, seqno, ph) do { \
XlapTimestampPlaceholder *__ph = (ph); \
XlapTimestampTable *__ts = atomic_load_explicit(&(sck)->tstable[kind], memory_order_acquire); \
for (unsigned int __t = 0; __t < ts_count; __t++) { \
__ts->rows[seqno % TS_ROWS].time[__t].actual.t.tv_sec += __ph->rows[0].time[__t].actual.t.tv_sec; \
__ts->rows[seqno % TS_ROWS].time[__t].actual.t.tv_nsec += __ph->rows[0].time[__t].actual.t.tv_nsec; \
__ts->rows[seqno % TS_ROWS].time[__t].actual.c += __ph->rows[0].time[__t].actual.c; \
} \
} while (0)
#else
# define XlapTimestampPlaceholderUse(sck, kind, seqno, ph) do { \
(void) (sck); \
(void) (kind); \
(void) (seqno); \
(void) (ph); \
} while (0)
#endif
#endif // XLAP_H
data_files:
sender: "rtn2017/results/on/2017_03_28_09_33_00_Sender.csv"
receiver: "rtn2017/results/on/2017_03_28_09_33_00_Receiver.csv"
cycle_reference:
sender:
Start: PrrtSendStart
Stop: LinkTransmitEnd
receiver:
Start: PrrtReceivePackage
Stop: PrrtDeliver
time_reference:
sender:
Start: PrrtSendStart
Stop: LinkTransmitEnd
receiver:
Start: LinkReceive
Stop: PrrtDeliver
stamps:
PrrtSendStart:
Source: sender
Type: time
PrrtSendEnd:
Source: sender
Type: time
PrrtSubmitPackage:
Source: sender
Type: cycle
PrrtEncodeStart:
Source: sender
Type: cycle
PrrtEncodeEnd:
Source: sender
Type: cycle
PrrtTransmitStart:
Source: sender
Type: cycle
PrrtTransmitStart:
Source: sender
Type: cycle
PrrtTransmitEnd:
Source: sender
Type: cycle
LinkTransmitStart:
Source: sender
Type: cycle
LinkTransmitEnd:
Source: sender
Type: time
LinkReceive:
Source: receiver
Type: time
PrrtDeliver:
Source: receiver
Type: time
SendFeedbackStart:
Source: receiver
Type: cycle
SendFeedbackEnd:
Source: receiver
Type: cycle
DecodeStart:
Source: receiver
Type: cycle
DecodeEnd:
Source: receiver
Type: cycle
HandlePacketStart:
Source: receiver
Type: cycle
HandlePacketEnd:
Source: receiver
Type: cycle
CopyOutputStart:
Source: receiver
Type: cycle
CopyOutputEnd:
Source: receiver
Type: cycle
PrrtReturnPackage:
Source: receiver
Type: cycle
PrrtReceivePackage:
Source: receiver
Type: time
durations:
Send:
Start: PrrtSendStart
Stop: PrrtSendEnd
Source: sender
PrrtTransmit:
Start: PrrtTransmitStart
Stop: PrrtTransmitEnd
Source: sender
LinkTransmit:
Start: LinkTransmitStart
Stop: LinkTransmitEnd
Source: sender
Submit:
Start: PrrtSendStart
Stop: PrrtSubmitPackage
Source: sender
Enqueue:
Start: PrrtSubmitPackage
Stop: PrrtSendEnd
Source: sender
SenderIPC:
Start: PrrtSubmitPackage
Stop: PrrtTransmitStart
Source: sender
SenderEnqueued:
Start: PrrtSendEnd
Stop: LinkTransmitStart
Source: sender
ReceiverIPC:
Start: PrrtReturnPackage
Stop: PrrtReceivePackage
Source: receiver
HandlePacket:
Start: HandlePacketStart
Stop: HandlePacketEnd
Source: receiver
Feedback:
Start: SendFeedbackStart
Stop: SendFeedbackEnd
Source: receiver
Decoding:
Start: DecodeStart
Stop: DecodeEnd
Source: receiver
packet_types:
Data: 0
Redundancy: 1
__pycache__/
import math
import matplotlib.pyplot as plt
from xlap.analyse.util import extract_durations
def correlation(data_frame, config, export=False, file_name="Correlation.pdf"):
durations = [x + "_D" for x in extract_durations(config)]
durations.remove("EndToEnd_D")
cols = 4
rows = int(math.ceil(len(durations) / cols))
fig, axes = plt.subplots(nrows=rows, ncols=cols)
fig.set_size_inches(4 * cols, 3.5 * rows, forward=True)
i = 0
for duration in durations:
ax = data_frame.plot.scatter(ax=axes[i // cols, i % cols], y="EndToEnd_D", x=duration, grid=True, marker="+",
color="black")
ax.set_ylabel("EndToEnd [us]")
ax.margins(0.1, 0.1)
ax.set_xlabel("{} [us]".format(duration.replace("_D", "")))
i += 1
if export and file_name is not None:
fig.savefig(file_name)
plt.tight_layout()
plt.show()
\ No newline at end of file
import pandas as pd
import matplotlib.pyplot as plt
from xlap.analyse.util import get_outlier_threshold, extract_durations, box
def jitter_causes(df, durations, export=False, file_name=None):
stats = df["EndToEnd_D"].describe()
threshold = get_outlier_threshold(stats)
outliers = df[df["EndToEnd_D"] > threshold]
reasons = [d + "_D" for d in durations.keys()]
df_reasons = pd.DataFrame(index=outliers.index)
for reason in reasons:
reason_threshold = get_outlier_threshold(df[reason].describe())
df_reasons[reason] = 0
df_reasons[reason] = outliers[outliers[reason] > reason_threshold].notnull()
df_sum = df_reasons.sum().sort_values(ascending=False)
ax = df_sum.plot.bar(x="Reason", y="Frequency", rot=45, grid=True, legend=False, color="black")
fig = ax.get_figure()
plt.ylabel("Frequency")
ax.set_xticklabels(list(map(lambda x: x.get_text().replace("_D", ""), ax.get_xticklabels())))
fig.set_size_inches(8, 3, forward=True)
if export:
fig.savefig(file_name)
print("Outliers:", len(outliers), ";", "Threshold[us]:", threshold)
def trace_jitter(data_frame, export=False, file_name=None):
"""
Displays (and saves) a stacked boxplot of durations.
"""
thresh = get_outlier_threshold(data_frame["EndToEnd_D"].describe())
df_no_outliers = data_frame[data_frame["EndToEnd_D"] <= thresh]
box(df_no_outliers, export, file_name)
print("{} / {} are no outliers.".format(len(df_no_outliers), len(data_frame)))
fig = plt.gcf()
fig.canvas.set_window_title('Jitter Analysis')
plt.show()
def prep(df, config):
return df[[x + "_D" for x in extract_durations(config)]]
from sklearn import linear_model
import matplotlib.pyplot as plt
def linear(data_frame, column):
"""
Execute a simple linear regression on the given column for the passed data_frame.
:param data_frame:
:param column:
:return:
"""
x = data_frame.index.values.reshape(-1, 1)
y = data_frame[column].values
model = linear_model.LinearRegression()
model.fit(x, y)
print("R-Score:", model.score(x, y))
plt.scatter(x, y)
plt.grid()
plt.plot(x, model.predict(x), color="red", linewidth=3)
plt.show()
import numpy as np
import matplotlib.pyplot as plt
from xlap.analyse.util import extract_durations
from ipywidgets import interact
import ipywidgets as widgets
def _create_line(config):
tr = config["time_reference"]
color = {
"sender": "#AAAAAA",
"receiver": "#888888",
"e2e": "black"
}
def _creator(duration_name):
if duration_name == "EndToEnd":
return [tr["sender"]["Start"] + "_T", tr["receiver"]["Stop"] + "_T", color["e2e"], "EndToEnd"]
elif duration_name == "Sender":
return [tr["sender"]["Start"] + "_T", tr["sender"]["Stop"] + "_T", color["sender"], "Sender"]
elif duration_name == "Receiver":
return [tr["receiver"]["Start"] + "_T", tr["receiver"]["Stop"] + "_T", color["receiver"], "Receiver"]
else:
duration = config["durations"][duration_name]
return [duration["Start"] + "_T", duration["Stop"] + "_T", color[duration["Source"]], duration_name]
return _creator
def trace(data_frame, config, export=False, file_name="TraceJitter.pdf"):
"""
:param data_frame:
:param config:
:param export:
:param file_name:
:return:
"""
fig, ax = plt.subplots(figsize=(8, 4.5))
plt.grid()
line_creator = _create_line(config)
durations = [line_creator(x) for x in extract_durations(config)]
series = np.transpose(np.array(durations))
n = series.shape[1]
# Starts and Ends
tr = config["time_reference"]
base = data_frame[tr["sender"]["Start"] + "_T"]
starts = data_frame[series[0]] - base
ends = data_frame[series[1]] - base
plt.hlines(range(n), starts, ends, series[2], linewidths=[5])
plt.xlabel("Time [us]")
fig.canvas.draw()
ax.set_yticklabels(series[3])
ax.yaxis.set_ticks(np.arange(0, n, 1))
if export:
plt.savefig(file_name)
plt.show()
def traces(df, config):
"""
Display a slider to select sequence numbers and the respective trace.
"""
@interact(seq_no=widgets.IntSlider(min=1, max=len(df), step=1, value=47))
def _f(seq_no):
trace(df.ix[seq_no], config)
import matplotlib.pyplot as plt
from matplotlib import pyplot as plt
def hist(df):
return df.hist(cumulative=True, normed=1, bins=200)
def scatter(df, column):
plt.scatter(df.index, df[column], grid=True)
def get_outlier_threshold(stats):
q75 = stats["75%"]
iqr = q75 - stats["25%"]
return q75 + 1.5 * iqr
def extract_durations(config):
durations = config["durations"]
durations_send = [x for x in durations if durations[x]["Source"] == "sender"]
durations_recv = [x for x in durations if durations[x]["Source"] == "receiver"]
return ["EndToEnd", "Sender"] + durations_send + ["Receiver"] + durations_recv
def box(data_frame, export=False, file_name=None):
"""
Display a boxplot for the durations contained in data_frame.
:param data_frame:
:param export:
:param file_name:
:return:
"""
ax = data_frame.plot.box(vert=False, grid=True)
fig = ax.get_figure()
ax.set_yticklabels(list(map(lambda x: x.get_text().replace("_D", ""), ax.get_yticklabels())))
plt.xlabel("Time [us]")
fig.set_size_inches(8, 4.5, forward=True)
if export and file_name is not None:
fig.savefig(file_name)
def describe_table(df):
stats = df.describe()
stats.drop(["count"], inplace=True)
stats.columns = list(map(lambda x: x.replace("_D", ""), stats.columns))
table = stats.to_latex(float_format=lambda x: "%.3f" % x)
print(table)
return stats
\ No newline at end of file
import argparse
from xlap.parse import evaluate, parse_config
import xlap.analyse.jitter as jitter