Deploying to gh-pages from @ eunomia-bpf/bpf-developer-tutorial@52ae3ae26d 🚀
1
.nojekyll
Normal file
@@ -0,0 +1 @@
|
||||
This file makes sure that Github Pages doesn't process mdBook's output.
|
||||
322
0-introduce/index.html
Normal file
BIN
0-introduce/kernel-arch.webp
Normal file
|
After Width: | Height: | Size: 35 KiB |
BIN
0-introduce/new-os-model.jpg
Normal file
|
After Width: | Height: | Size: 46 KiB |
7
1-helloworld/.gitignore
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
.vscode
|
||||
package.json
|
||||
*.o
|
||||
*.skel.json
|
||||
*.skel.yaml
|
||||
package.yaml
|
||||
ecli
|
||||
311
1-helloworld/index.html
Normal file
21
1-helloworld/minimal.bpf.c
Normal file
@@ -0,0 +1,21 @@
|
||||
/* SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) */
|
||||
#define BPF_NO_GLOBAL_DATA
|
||||
#include <linux/bpf.h>
|
||||
#include <bpf/bpf_helpers.h>
|
||||
#include <bpf/bpf_tracing.h>
|
||||
|
||||
typedef unsigned int u32;
|
||||
typedef int pid_t;
|
||||
const pid_t pid_filter = 0;
|
||||
|
||||
char LICENSE[] SEC("license") = "Dual BSD/GPL";
|
||||
|
||||
SEC("tp/syscalls/sys_enter_write")
|
||||
int handle_tp(void *ctx)
|
||||
{
|
||||
pid_t pid = bpf_get_current_pid_tgid() >> 32;
|
||||
if (pid_filter && pid != pid_filter)
|
||||
return 0;
|
||||
bpf_printk("BPF triggered from PID %d.\n", pid);
|
||||
return 0;
|
||||
}
|
||||
7
10-hardirqs/.gitignore
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
.vscode
|
||||
package.json
|
||||
*.o
|
||||
*.skel.json
|
||||
*.skel.yaml
|
||||
package.yaml
|
||||
ecli
|
||||
31
10-hardirqs/bits.bpf.h
Normal file
@@ -0,0 +1,31 @@
|
||||
/* SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) */
|
||||
#ifndef __BITS_BPF_H
|
||||
#define __BITS_BPF_H
|
||||
|
||||
#define READ_ONCE(x) (*(volatile typeof(x) *)&(x))
|
||||
#define WRITE_ONCE(x, val) ((*(volatile typeof(x) *)&(x)) = val)
|
||||
|
||||
static __always_inline u64 log2(u32 v)
|
||||
{
|
||||
u32 shift, r;
|
||||
|
||||
r = (v > 0xFFFF) << 4; v >>= r;
|
||||
shift = (v > 0xFF) << 3; v >>= shift; r |= shift;
|
||||
shift = (v > 0xF) << 2; v >>= shift; r |= shift;
|
||||
shift = (v > 0x3) << 1; v >>= shift; r |= shift;
|
||||
r |= (v >> 1);
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
static __always_inline u64 log2l(u64 v)
|
||||
{
|
||||
u32 hi = v >> 32;
|
||||
|
||||
if (hi)
|
||||
return log2(hi) + 32;
|
||||
else
|
||||
return log2(v);
|
||||
}
|
||||
|
||||
#endif /* __BITS_BPF_H */
|
||||
135
10-hardirqs/hardirqs.bpf.c
Normal file
@@ -0,0 +1,135 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
// Copyright (c) 2020 Wenbo Zhang
|
||||
#include <vmlinux.h>
|
||||
#include <bpf/bpf_core_read.h>
|
||||
#include <bpf/bpf_helpers.h>
|
||||
#include <bpf/bpf_tracing.h>
|
||||
#include "hardirqs.h"
|
||||
#include "bits.bpf.h"
|
||||
#include "maps.bpf.h"
|
||||
|
||||
#define MAX_ENTRIES 256
|
||||
|
||||
const volatile bool filter_cg = false;
|
||||
const volatile bool targ_dist = false;
|
||||
const volatile bool targ_ns = false;
|
||||
const volatile bool do_count = false;
|
||||
|
||||
struct irq_key {
|
||||
char name[32];
|
||||
};
|
||||
|
||||
struct {
|
||||
__uint(type, BPF_MAP_TYPE_CGROUP_ARRAY);
|
||||
__type(key, u32);
|
||||
__type(value, u32);
|
||||
__uint(max_entries, 1);
|
||||
} cgroup_map SEC(".maps");
|
||||
|
||||
struct {
|
||||
__uint(type, BPF_MAP_TYPE_PERCPU_ARRAY);
|
||||
__uint(max_entries, 1);
|
||||
__type(key, u32);
|
||||
__type(value, u64);
|
||||
} start SEC(".maps");
|
||||
|
||||
/// @sample {"interval": 1000, "type" : "log2_hist"}
|
||||
struct {
|
||||
__uint(type, BPF_MAP_TYPE_HASH);
|
||||
__uint(max_entries, MAX_ENTRIES);
|
||||
__type(key, struct irq_key);
|
||||
__type(value, struct info);
|
||||
} infos SEC(".maps");
|
||||
|
||||
static struct info zero;
|
||||
|
||||
static int handle_entry(int irq, struct irqaction *action)
|
||||
{
|
||||
if (filter_cg && !bpf_current_task_under_cgroup(&cgroup_map, 0))
|
||||
return 0;
|
||||
|
||||
if (do_count) {
|
||||
struct irq_key key = {};
|
||||
struct info *info;
|
||||
|
||||
bpf_probe_read_kernel_str(&key.name, sizeof(key.name), BPF_CORE_READ(action, name));
|
||||
info = bpf_map_lookup_or_try_init(&infos, &key, &zero);
|
||||
if (!info)
|
||||
return 0;
|
||||
info->count += 1;
|
||||
return 0;
|
||||
} else {
|
||||
u64 ts = bpf_ktime_get_ns();
|
||||
u32 key = 0;
|
||||
|
||||
if (filter_cg && !bpf_current_task_under_cgroup(&cgroup_map, 0))
|
||||
return 0;
|
||||
|
||||
bpf_map_update_elem(&start, &key, &ts, BPF_ANY);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static int handle_exit(int irq, struct irqaction *action)
|
||||
{
|
||||
struct irq_key ikey = {};
|
||||
struct info *info;
|
||||
u32 key = 0;
|
||||
u64 delta;
|
||||
u64 *tsp;
|
||||
|
||||
if (filter_cg && !bpf_current_task_under_cgroup(&cgroup_map, 0))
|
||||
return 0;
|
||||
|
||||
tsp = bpf_map_lookup_elem(&start, &key);
|
||||
if (!tsp)
|
||||
return 0;
|
||||
|
||||
delta = bpf_ktime_get_ns() - *tsp;
|
||||
if (!targ_ns)
|
||||
delta /= 1000U;
|
||||
|
||||
bpf_probe_read_kernel_str(&ikey.name, sizeof(ikey.name), BPF_CORE_READ(action, name));
|
||||
info = bpf_map_lookup_or_try_init(&infos, &ikey, &zero);
|
||||
if (!info)
|
||||
return 0;
|
||||
|
||||
if (!targ_dist) {
|
||||
info->count += delta;
|
||||
} else {
|
||||
u64 slot;
|
||||
|
||||
slot = log2(delta);
|
||||
if (slot >= MAX_SLOTS)
|
||||
slot = MAX_SLOTS - 1;
|
||||
info->slots[slot]++;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
SEC("tp_btf/irq_handler_entry")
|
||||
int BPF_PROG(irq_handler_entry_btf, int irq, struct irqaction *action)
|
||||
{
|
||||
return handle_entry(irq, action);
|
||||
}
|
||||
|
||||
SEC("tp_btf/irq_handler_exit")
|
||||
int BPF_PROG(irq_handler_exit_btf, int irq, struct irqaction *action)
|
||||
{
|
||||
return handle_exit(irq, action);
|
||||
}
|
||||
|
||||
SEC("raw_tp/irq_handler_entry")
|
||||
int BPF_PROG(irq_handler_entry, int irq, struct irqaction *action)
|
||||
{
|
||||
return handle_entry(irq, action);
|
||||
}
|
||||
|
||||
SEC("raw_tp/irq_handler_exit")
|
||||
int BPF_PROG(irq_handler_exit, int irq, struct irqaction *action)
|
||||
{
|
||||
return handle_exit(irq, action);
|
||||
}
|
||||
|
||||
char LICENSE[] SEC("license") = "GPL";
|
||||
12
10-hardirqs/hardirqs.h
Normal file
@@ -0,0 +1,12 @@
|
||||
/* SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) */
|
||||
#ifndef __HARDIRQS_H
|
||||
#define __HARDIRQS_H
|
||||
|
||||
#define MAX_SLOTS 20
|
||||
|
||||
struct info {
|
||||
__u64 count;
|
||||
__u32 slots[MAX_SLOTS];
|
||||
};
|
||||
|
||||
#endif /* __HARDIRQS_H */
|
||||
353
10-hardirqs/index.html
Normal file
26
10-hardirqs/maps.bpf.h
Normal file
@@ -0,0 +1,26 @@
|
||||
// SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause)
|
||||
// Copyright (c) 2020 Anton Protopopov
|
||||
#ifndef __MAPS_BPF_H
|
||||
#define __MAPS_BPF_H
|
||||
|
||||
#include <bpf/bpf_helpers.h>
|
||||
#include <asm-generic/errno.h>
|
||||
|
||||
static __always_inline void *
|
||||
bpf_map_lookup_or_try_init(void *map, const void *key, const void *init)
|
||||
{
|
||||
void *val;
|
||||
long err;
|
||||
|
||||
val = bpf_map_lookup_elem(map, key);
|
||||
if (val)
|
||||
return val;
|
||||
|
||||
err = bpf_map_update_elem(map, key, init, BPF_NOEXIST);
|
||||
if (err && err != -EEXIST)
|
||||
return 0;
|
||||
|
||||
return bpf_map_lookup_elem(map, key);
|
||||
}
|
||||
|
||||
#endif /* __MAPS_BPF_H */
|
||||
7
11-bootstrap/.gitignore
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
.vscode
|
||||
package.json
|
||||
*.o
|
||||
*.skel.json
|
||||
*.skel.yaml
|
||||
package.yaml
|
||||
ecli
|
||||
112
11-bootstrap/bootstrap.bpf.c
Normal file
@@ -0,0 +1,112 @@
|
||||
// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
|
||||
/* Copyright (c) 2020 Facebook */
|
||||
#include "vmlinux.h"
|
||||
#include <bpf/bpf_helpers.h>
|
||||
#include <bpf/bpf_tracing.h>
|
||||
#include <bpf/bpf_core_read.h>
|
||||
#include "bootstrap.h"
|
||||
|
||||
char LICENSE[] SEC("license") = "Dual BSD/GPL";
|
||||
|
||||
struct {
|
||||
__uint(type, BPF_MAP_TYPE_HASH);
|
||||
__uint(max_entries, 8192);
|
||||
__type(key, pid_t);
|
||||
__type(value, u64);
|
||||
} exec_start SEC(".maps");
|
||||
|
||||
struct {
|
||||
__uint(type, BPF_MAP_TYPE_RINGBUF);
|
||||
__uint(max_entries, 256 * 1024);
|
||||
} rb SEC(".maps");
|
||||
|
||||
const volatile unsigned long long min_duration_ns = 0;
|
||||
|
||||
SEC("tp/sched/sched_process_exec")
|
||||
int handle_exec(struct trace_event_raw_sched_process_exec *ctx)
|
||||
{
|
||||
struct task_struct *task;
|
||||
unsigned fname_off;
|
||||
struct event *e;
|
||||
pid_t pid;
|
||||
u64 ts;
|
||||
|
||||
/* remember time exec() was executed for this PID */
|
||||
pid = bpf_get_current_pid_tgid() >> 32;
|
||||
ts = bpf_ktime_get_ns();
|
||||
bpf_map_update_elem(&exec_start, &pid, &ts, BPF_ANY);
|
||||
|
||||
/* don't emit exec events when minimum duration is specified */
|
||||
if (min_duration_ns)
|
||||
return 0;
|
||||
|
||||
/* reserve sample from BPF ringbuf */
|
||||
e = bpf_ringbuf_reserve(&rb, sizeof(*e), 0);
|
||||
if (!e)
|
||||
return 0;
|
||||
|
||||
/* fill out the sample with data */
|
||||
task = (struct task_struct *)bpf_get_current_task();
|
||||
|
||||
e->exit_event = false;
|
||||
e->pid = pid;
|
||||
e->ppid = BPF_CORE_READ(task, real_parent, tgid);
|
||||
bpf_get_current_comm(&e->comm, sizeof(e->comm));
|
||||
|
||||
fname_off = ctx->__data_loc_filename & 0xFFFF;
|
||||
bpf_probe_read_str(&e->filename, sizeof(e->filename), (void *)ctx + fname_off);
|
||||
|
||||
/* successfully submit it to user-space for post-processing */
|
||||
bpf_ringbuf_submit(e, 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
SEC("tp/sched/sched_process_exit")
|
||||
int handle_exit(struct trace_event_raw_sched_process_template* ctx)
|
||||
{
|
||||
struct task_struct *task;
|
||||
struct event *e;
|
||||
pid_t pid, tid;
|
||||
u64 id, ts, *start_ts, duration_ns = 0;
|
||||
|
||||
/* get PID and TID of exiting thread/process */
|
||||
id = bpf_get_current_pid_tgid();
|
||||
pid = id >> 32;
|
||||
tid = (u32)id;
|
||||
|
||||
/* ignore thread exits */
|
||||
if (pid != tid)
|
||||
return 0;
|
||||
|
||||
/* if we recorded start of the process, calculate lifetime duration */
|
||||
start_ts = bpf_map_lookup_elem(&exec_start, &pid);
|
||||
if (start_ts)
|
||||
duration_ns = bpf_ktime_get_ns() - *start_ts;
|
||||
else if (min_duration_ns)
|
||||
return 0;
|
||||
bpf_map_delete_elem(&exec_start, &pid);
|
||||
|
||||
/* if process didn't live long enough, return early */
|
||||
if (min_duration_ns && duration_ns < min_duration_ns)
|
||||
return 0;
|
||||
|
||||
/* reserve sample from BPF ringbuf */
|
||||
e = bpf_ringbuf_reserve(&rb, sizeof(*e), 0);
|
||||
if (!e)
|
||||
return 0;
|
||||
|
||||
/* fill out the sample with data */
|
||||
task = (struct task_struct *)bpf_get_current_task();
|
||||
|
||||
e->exit_event = true;
|
||||
e->duration_ns = duration_ns;
|
||||
e->pid = pid;
|
||||
e->ppid = BPF_CORE_READ(task, real_parent, tgid);
|
||||
e->exit_code = (BPF_CORE_READ(task, exit_code) >> 8) & 0xff;
|
||||
bpf_get_current_comm(&e->comm, sizeof(e->comm));
|
||||
|
||||
/* send data to user-space for post-processing */
|
||||
bpf_ringbuf_submit(e, 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
19
11-bootstrap/bootstrap.h
Normal file
@@ -0,0 +1,19 @@
|
||||
/* SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) */
|
||||
/* Copyright (c) 2020 Facebook */
|
||||
#ifndef __BOOTSTRAP_H
|
||||
#define __BOOTSTRAP_H
|
||||
|
||||
#define TASK_COMM_LEN 16
|
||||
#define MAX_FILENAME_LEN 127
|
||||
|
||||
struct event {
|
||||
int pid;
|
||||
int ppid;
|
||||
unsigned exit_code;
|
||||
unsigned long long duration_ns;
|
||||
char comm[TASK_COMM_LEN];
|
||||
char filename[MAX_FILENAME_LEN];
|
||||
bool exit_event;
|
||||
};
|
||||
|
||||
#endif /* __BOOTSTRAP_H */
|
||||
340
11-bootstrap/index.html
Normal file
2
13-tcpconnlat/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
.vscode
|
||||
package.json
|
||||
336
13-tcpconnlat/index.html
Normal file
131
13-tcpconnlat/libbpf-bootstrap/tcpconnlat.bpf.c
Normal file
@@ -0,0 +1,131 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
// Copyright (c) 2020 Wenbo Zhang
|
||||
#include <vmlinux.h>
|
||||
#include <bpf/bpf_helpers.h>
|
||||
#include <bpf/bpf_core_read.h>
|
||||
#include <bpf/bpf_tracing.h>
|
||||
#include "tcpconnlat.h"
|
||||
|
||||
#define AF_INET 2
|
||||
#define AF_INET6 10
|
||||
|
||||
const volatile __u64 targ_min_us = 0;
|
||||
const volatile pid_t targ_tgid = 0;
|
||||
|
||||
struct piddata {
|
||||
char comm[TASK_COMM_LEN];
|
||||
u64 ts;
|
||||
u32 tgid;
|
||||
};
|
||||
|
||||
struct {
|
||||
__uint(type, BPF_MAP_TYPE_HASH);
|
||||
__uint(max_entries, 4096);
|
||||
__type(key, struct sock *);
|
||||
__type(value, struct piddata);
|
||||
} start SEC(".maps");
|
||||
|
||||
struct {
|
||||
__uint(type, BPF_MAP_TYPE_PERF_EVENT_ARRAY);
|
||||
__uint(key_size, sizeof(u32));
|
||||
__uint(value_size, sizeof(u32));
|
||||
} events SEC(".maps");
|
||||
|
||||
static int trace_connect(struct sock *sk)
|
||||
{
|
||||
u32 tgid = bpf_get_current_pid_tgid() >> 32;
|
||||
struct piddata piddata = {};
|
||||
|
||||
if (targ_tgid && targ_tgid != tgid)
|
||||
return 0;
|
||||
|
||||
bpf_get_current_comm(&piddata.comm, sizeof(piddata.comm));
|
||||
piddata.ts = bpf_ktime_get_ns();
|
||||
piddata.tgid = tgid;
|
||||
bpf_map_update_elem(&start, &sk, &piddata, 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int handle_tcp_rcv_state_process(void *ctx, struct sock *sk)
|
||||
{
|
||||
struct piddata *piddatap;
|
||||
struct event event = {};
|
||||
s64 delta;
|
||||
u64 ts;
|
||||
|
||||
if (BPF_CORE_READ(sk, __sk_common.skc_state) != TCP_SYN_SENT)
|
||||
return 0;
|
||||
|
||||
piddatap = bpf_map_lookup_elem(&start, &sk);
|
||||
if (!piddatap)
|
||||
return 0;
|
||||
|
||||
ts = bpf_ktime_get_ns();
|
||||
delta = (s64)(ts - piddatap->ts);
|
||||
if (delta < 0)
|
||||
goto cleanup;
|
||||
|
||||
event.delta_us = delta / 1000U;
|
||||
if (targ_min_us && event.delta_us < targ_min_us)
|
||||
goto cleanup;
|
||||
__builtin_memcpy(&event.comm, piddatap->comm,
|
||||
sizeof(event.comm));
|
||||
event.ts_us = ts / 1000;
|
||||
event.tgid = piddatap->tgid;
|
||||
event.lport = BPF_CORE_READ(sk, __sk_common.skc_num);
|
||||
event.dport = BPF_CORE_READ(sk, __sk_common.skc_dport);
|
||||
event.af = BPF_CORE_READ(sk, __sk_common.skc_family);
|
||||
if (event.af == AF_INET) {
|
||||
event.saddr_v4 = BPF_CORE_READ(sk, __sk_common.skc_rcv_saddr);
|
||||
event.daddr_v4 = BPF_CORE_READ(sk, __sk_common.skc_daddr);
|
||||
} else {
|
||||
BPF_CORE_READ_INTO(&event.saddr_v6, sk,
|
||||
__sk_common.skc_v6_rcv_saddr.in6_u.u6_addr32);
|
||||
BPF_CORE_READ_INTO(&event.daddr_v6, sk,
|
||||
__sk_common.skc_v6_daddr.in6_u.u6_addr32);
|
||||
}
|
||||
bpf_perf_event_output(ctx, &events, BPF_F_CURRENT_CPU,
|
||||
&event, sizeof(event));
|
||||
|
||||
cleanup:
|
||||
bpf_map_delete_elem(&start, &sk);
|
||||
return 0;
|
||||
}
|
||||
|
||||
SEC("kprobe/tcp_v4_connect")
|
||||
int BPF_KPROBE(tcp_v4_connect, struct sock *sk)
|
||||
{
|
||||
return trace_connect(sk);
|
||||
}
|
||||
|
||||
SEC("kprobe/tcp_v6_connect")
|
||||
int BPF_KPROBE(tcp_v6_connect, struct sock *sk)
|
||||
{
|
||||
return trace_connect(sk);
|
||||
}
|
||||
|
||||
SEC("kprobe/tcp_rcv_state_process")
|
||||
int BPF_KPROBE(tcp_rcv_state_process, struct sock *sk)
|
||||
{
|
||||
return handle_tcp_rcv_state_process(ctx, sk);
|
||||
}
|
||||
|
||||
SEC("fentry/tcp_v4_connect")
|
||||
int BPF_PROG(fentry_tcp_v4_connect, struct sock *sk)
|
||||
{
|
||||
return trace_connect(sk);
|
||||
}
|
||||
|
||||
SEC("fentry/tcp_v6_connect")
|
||||
int BPF_PROG(fentry_tcp_v6_connect, struct sock *sk)
|
||||
{
|
||||
return trace_connect(sk);
|
||||
}
|
||||
|
||||
SEC("fentry/tcp_rcv_state_process")
|
||||
int BPF_PROG(fentry_tcp_rcv_state_process, struct sock *sk)
|
||||
{
|
||||
return handle_tcp_rcv_state_process(ctx, sk);
|
||||
}
|
||||
|
||||
char LICENSE[] SEC("license") = "GPL";
|
||||
298
13-tcpconnlat/libbpf-bootstrap/tcpconnlat.c
Normal file
@@ -0,0 +1,298 @@
|
||||
// SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause)
|
||||
// Copyright (c) 2020 Wenbo Zhang
|
||||
//
|
||||
// Based on tcpconnlat(8) from BCC by Brendan Gregg.
|
||||
// 11-Jul-2020 Wenbo Zhang Created this.
|
||||
#include "tcpconnlat.h"
|
||||
#include <argp.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <bpf/bpf.h>
|
||||
#include <bpf/btf.h>
|
||||
#include <bpf/libbpf.h>
|
||||
#include <signal.h>
|
||||
#include <stdio.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
#include "tcpconnlat.skel.h"
|
||||
// #include "trace_helpers.h"
|
||||
|
||||
#define PERF_BUFFER_PAGES 16
|
||||
#define PERF_POLL_TIMEOUT_MS 100
|
||||
|
||||
static volatile sig_atomic_t exiting = 0;
|
||||
|
||||
static struct env {
|
||||
__u64 min_us;
|
||||
pid_t pid;
|
||||
bool timestamp;
|
||||
bool lport;
|
||||
bool verbose;
|
||||
} env;
|
||||
|
||||
const char* argp_program_version = "tcpconnlat 0.1";
|
||||
const char* argp_program_bug_address =
|
||||
"https://github.com/iovisor/bcc/tree/master/libbpf-tools";
|
||||
const char argp_program_doc[] =
|
||||
"\nTrace TCP connects and show connection latency.\n"
|
||||
"\n"
|
||||
"USAGE: tcpconnlat [--help] [-t] [-p PID] [-L]\n"
|
||||
"\n"
|
||||
"EXAMPLES:\n"
|
||||
" tcpconnlat # summarize on-CPU time as a histogram\n"
|
||||
" tcpconnlat 1 # trace connection latency slower than 1 ms\n"
|
||||
" tcpconnlat 0.1 # trace connection latency slower than 100 "
|
||||
"us\n"
|
||||
" tcpconnlat -t # 1s summaries, milliseconds, and timestamps\n"
|
||||
" tcpconnlat -p 185 # trace PID 185 only\n"
|
||||
" tcpconnlat -L # include LPORT while printing outputs\n";
|
||||
|
||||
static const struct argp_option opts[] = {
|
||||
{"timestamp", 't', NULL, 0, "Include timestamp on output"},
|
||||
{"pid", 'p', "PID", 0, "Trace this PID only"},
|
||||
{"lport", 'L', NULL, 0, "Include LPORT on output"},
|
||||
{"verbose", 'v', NULL, 0, "Verbose debug output"},
|
||||
{NULL, 'h', NULL, OPTION_HIDDEN, "Show the full help"},
|
||||
{},
|
||||
};
|
||||
|
||||
static error_t parse_arg(int key, char* arg, struct argp_state* state) {
|
||||
static int pos_args;
|
||||
|
||||
switch (key) {
|
||||
case 'h':
|
||||
argp_state_help(state, stderr, ARGP_HELP_STD_HELP);
|
||||
break;
|
||||
case 'v':
|
||||
env.verbose = true;
|
||||
break;
|
||||
case 'p':
|
||||
errno = 0;
|
||||
env.pid = strtol(arg, NULL, 10);
|
||||
if (errno) {
|
||||
fprintf(stderr, "invalid PID: %s\n", arg);
|
||||
argp_usage(state);
|
||||
}
|
||||
break;
|
||||
case 't':
|
||||
env.timestamp = true;
|
||||
break;
|
||||
case 'L':
|
||||
env.lport = true;
|
||||
break;
|
||||
case ARGP_KEY_ARG:
|
||||
if (pos_args++) {
|
||||
fprintf(stderr, "Unrecognized positional argument: %s\n", arg);
|
||||
argp_usage(state);
|
||||
}
|
||||
errno = 0;
|
||||
env.min_us = strtod(arg, NULL) * 1000;
|
||||
if (errno || env.min_us <= 0) {
|
||||
fprintf(stderr, "Invalid delay (in us): %s\n", arg);
|
||||
argp_usage(state);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return ARGP_ERR_UNKNOWN;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int libbpf_print_fn(enum libbpf_print_level level,
|
||||
const char* format,
|
||||
va_list args) {
|
||||
if (level == LIBBPF_DEBUG && !env.verbose)
|
||||
return 0;
|
||||
return vfprintf(stderr, format, args);
|
||||
}
|
||||
|
||||
static void sig_int(int signo) {
|
||||
exiting = 1;
|
||||
}
|
||||
|
||||
void handle_event(void* ctx, int cpu, void* data, __u32 data_sz) {
|
||||
const struct event* e = data;
|
||||
char src[INET6_ADDRSTRLEN];
|
||||
char dst[INET6_ADDRSTRLEN];
|
||||
union {
|
||||
struct in_addr x4;
|
||||
struct in6_addr x6;
|
||||
} s, d;
|
||||
static __u64 start_ts;
|
||||
|
||||
if (env.timestamp) {
|
||||
if (start_ts == 0)
|
||||
start_ts = e->ts_us;
|
||||
printf("%-9.3f ", (e->ts_us - start_ts) / 1000000.0);
|
||||
}
|
||||
if (e->af == AF_INET) {
|
||||
s.x4.s_addr = e->saddr_v4;
|
||||
d.x4.s_addr = e->daddr_v4;
|
||||
} else if (e->af == AF_INET6) {
|
||||
memcpy(&s.x6.s6_addr, e->saddr_v6, sizeof(s.x6.s6_addr));
|
||||
memcpy(&d.x6.s6_addr, e->daddr_v6, sizeof(d.x6.s6_addr));
|
||||
} else {
|
||||
fprintf(stderr, "broken event: event->af=%d", e->af);
|
||||
return;
|
||||
}
|
||||
|
||||
if (env.lport) {
|
||||
printf("%-6d %-12.12s %-2d %-16s %-6d %-16s %-5d %.2f\n", e->tgid,
|
||||
e->comm, e->af == AF_INET ? 4 : 6,
|
||||
inet_ntop(e->af, &s, src, sizeof(src)), e->lport,
|
||||
inet_ntop(e->af, &d, dst, sizeof(dst)), ntohs(e->dport),
|
||||
e->delta_us / 1000.0);
|
||||
} else {
|
||||
printf("%-6d %-12.12s %-2d %-16s %-16s %-5d %.2f\n", e->tgid, e->comm,
|
||||
e->af == AF_INET ? 4 : 6, inet_ntop(e->af, &s, src, sizeof(src)),
|
||||
inet_ntop(e->af, &d, dst, sizeof(dst)), ntohs(e->dport),
|
||||
e->delta_us / 1000.0);
|
||||
}
|
||||
}
|
||||
|
||||
void handle_lost_events(void* ctx, int cpu, __u64 lost_cnt) {
|
||||
fprintf(stderr, "lost %llu events on CPU #%d\n", lost_cnt, cpu);
|
||||
}
|
||||
static bool fentry_try_attach(int id) {
|
||||
int prog_fd, attach_fd;
|
||||
char error[4096];
|
||||
struct bpf_insn insns[] = {
|
||||
{.code = BPF_ALU64 | BPF_MOV | BPF_K, .dst_reg = BPF_REG_0, .imm = 0},
|
||||
{.code = BPF_JMP | BPF_EXIT},
|
||||
};
|
||||
LIBBPF_OPTS(bpf_prog_load_opts, opts,
|
||||
.expected_attach_type = BPF_TRACE_FENTRY, .attach_btf_id = id,
|
||||
.log_buf = error, .log_size = sizeof(error), );
|
||||
|
||||
prog_fd = bpf_prog_load(BPF_PROG_TYPE_TRACING, "test", "GPL", insns,
|
||||
sizeof(insns) / sizeof(struct bpf_insn), &opts);
|
||||
if (prog_fd < 0)
|
||||
return false;
|
||||
|
||||
attach_fd = bpf_raw_tracepoint_open(NULL, prog_fd);
|
||||
if (attach_fd >= 0)
|
||||
close(attach_fd);
|
||||
|
||||
close(prog_fd);
|
||||
return attach_fd >= 0;
|
||||
}
|
||||
static bool fentry_can_attach(const char* name, const char* mod) {
|
||||
struct btf *btf, *vmlinux_btf, *module_btf = NULL;
|
||||
int err, id;
|
||||
|
||||
vmlinux_btf = btf__load_vmlinux_btf();
|
||||
err = libbpf_get_error(vmlinux_btf);
|
||||
if (err)
|
||||
return false;
|
||||
|
||||
btf = vmlinux_btf;
|
||||
|
||||
if (mod) {
|
||||
module_btf = btf__load_module_btf(mod, vmlinux_btf);
|
||||
err = libbpf_get_error(module_btf);
|
||||
if (!err)
|
||||
btf = module_btf;
|
||||
}
|
||||
|
||||
id = btf__find_by_name_kind(btf, name, BTF_KIND_FUNC);
|
||||
|
||||
btf__free(module_btf);
|
||||
btf__free(vmlinux_btf);
|
||||
return id > 0 && fentry_try_attach(id);
|
||||
}
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
static const struct argp argp = {
|
||||
.options = opts,
|
||||
.parser = parse_arg,
|
||||
.doc = argp_program_doc,
|
||||
};
|
||||
struct perf_buffer* pb = NULL;
|
||||
struct tcpconnlat_bpf* obj;
|
||||
int err;
|
||||
|
||||
err = argp_parse(&argp, argc, argv, 0, NULL, NULL);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
libbpf_set_strict_mode(LIBBPF_STRICT_ALL);
|
||||
libbpf_set_print(libbpf_print_fn);
|
||||
|
||||
obj = tcpconnlat_bpf__open();
|
||||
if (!obj) {
|
||||
fprintf(stderr, "failed to open BPF object\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* initialize global data (filtering options) */
|
||||
obj->rodata->targ_min_us = env.min_us;
|
||||
obj->rodata->targ_tgid = env.pid;
|
||||
|
||||
if (fentry_can_attach("tcp_v4_connect", NULL)) {
|
||||
bpf_program__set_attach_target(obj->progs.fentry_tcp_v4_connect, 0,
|
||||
"tcp_v4_connect");
|
||||
bpf_program__set_attach_target(obj->progs.fentry_tcp_v6_connect, 0,
|
||||
"tcp_v6_connect");
|
||||
bpf_program__set_attach_target(obj->progs.fentry_tcp_rcv_state_process,
|
||||
0, "tcp_rcv_state_process");
|
||||
bpf_program__set_autoload(obj->progs.tcp_v4_connect, false);
|
||||
bpf_program__set_autoload(obj->progs.tcp_v6_connect, false);
|
||||
bpf_program__set_autoload(obj->progs.tcp_rcv_state_process, false);
|
||||
} else {
|
||||
bpf_program__set_autoload(obj->progs.fentry_tcp_v4_connect, false);
|
||||
bpf_program__set_autoload(obj->progs.fentry_tcp_v6_connect, false);
|
||||
bpf_program__set_autoload(obj->progs.fentry_tcp_rcv_state_process,
|
||||
false);
|
||||
}
|
||||
|
||||
err = tcpconnlat_bpf__load(obj);
|
||||
if (err) {
|
||||
fprintf(stderr, "failed to load BPF object: %d\n", err);
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
err = tcpconnlat_bpf__attach(obj);
|
||||
if (err) {
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
pb = perf_buffer__new(bpf_map__fd(obj->maps.events), PERF_BUFFER_PAGES,
|
||||
handle_event, handle_lost_events, NULL, NULL);
|
||||
if (!pb) {
|
||||
fprintf(stderr, "failed to open perf buffer: %d\n", errno);
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
/* print header */
|
||||
if (env.timestamp)
|
||||
printf("%-9s ", ("TIME(s)"));
|
||||
if (env.lport) {
|
||||
printf("%-6s %-12s %-2s %-16s %-6s %-16s %-5s %s\n", "PID", "COMM",
|
||||
"IP", "SADDR", "LPORT", "DADDR", "DPORT", "LAT(ms)");
|
||||
} else {
|
||||
printf("%-6s %-12s %-2s %-16s %-16s %-5s %s\n", "PID", "COMM", "IP",
|
||||
"SADDR", "DADDR", "DPORT", "LAT(ms)");
|
||||
}
|
||||
|
||||
if (signal(SIGINT, sig_int) == SIG_ERR) {
|
||||
fprintf(stderr, "can't set signal handler: %s\n", strerror(errno));
|
||||
err = 1;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
/* main: poll */
|
||||
while (!exiting) {
|
||||
err = perf_buffer__poll(pb, PERF_POLL_TIMEOUT_MS);
|
||||
if (err < 0 && err != -EINTR) {
|
||||
fprintf(stderr, "error polling perf buffer: %s\n", strerror(-err));
|
||||
goto cleanup;
|
||||
}
|
||||
/* reset err to return 0 if exiting */
|
||||
err = 0;
|
||||
}
|
||||
|
||||
cleanup:
|
||||
perf_buffer__free(pb);
|
||||
tcpconnlat_bpf__destroy(obj);
|
||||
|
||||
return err != 0;
|
||||
}
|
||||
31
13-tcpconnlat/libbpf-bootstrap/tcpconnlat.h
Normal file
@@ -0,0 +1,31 @@
|
||||
/* SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) */
|
||||
#ifndef __TCPCONNLAT_H
|
||||
#define __TCPCONNLAT_H
|
||||
|
||||
// #include <inttypes.h>
|
||||
typedef unsigned char __u8;
|
||||
typedef unsigned short __u16;
|
||||
typedef unsigned int __u32;
|
||||
typedef unsigned long long __u64;
|
||||
|
||||
#define TASK_COMM_LEN 16
|
||||
|
||||
struct event {
|
||||
union {
|
||||
__u32 saddr_v4;
|
||||
__u8 saddr_v6[16];
|
||||
};
|
||||
union {
|
||||
__u32 daddr_v4;
|
||||
__u8 daddr_v6[16];
|
||||
};
|
||||
char comm[TASK_COMM_LEN];
|
||||
__u64 delta_us;
|
||||
__u64 ts_us;
|
||||
__u32 tgid;
|
||||
int af;
|
||||
__u16 lport;
|
||||
__u16 dport;
|
||||
};
|
||||
|
||||
#endif /* __TCPCONNLAT_H_ */
|
||||
113
13-tcpconnlat/tcpconnlat.bpf.c
Normal file
@@ -0,0 +1,113 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
// Copyright (c) 2020 Wenbo Zhang
|
||||
#include <vmlinux.h>
|
||||
#include <bpf/bpf_helpers.h>
|
||||
#include <bpf/bpf_core_read.h>
|
||||
#include <bpf/bpf_tracing.h>
|
||||
#include "tcpconnlat.bpf.h"
|
||||
|
||||
#define AF_INET 2
|
||||
#define AF_INET6 10
|
||||
|
||||
const volatile __u64 targ_min_us = 0;
|
||||
const volatile pid_t targ_tgid = 0;
|
||||
|
||||
struct piddata {
|
||||
char comm[TASK_COMM_LEN];
|
||||
u64 ts;
|
||||
u32 tgid;
|
||||
};
|
||||
|
||||
struct {
|
||||
__uint(type, BPF_MAP_TYPE_HASH);
|
||||
__uint(max_entries, 4096);
|
||||
__type(key, struct sock *);
|
||||
__type(value, struct piddata);
|
||||
} start SEC(".maps");
|
||||
|
||||
struct {
|
||||
__uint(type, BPF_MAP_TYPE_PERF_EVENT_ARRAY);
|
||||
__uint(key_size, sizeof(u32));
|
||||
__uint(value_size, sizeof(u32));
|
||||
} events SEC(".maps");
|
||||
|
||||
static int trace_connect(struct sock *sk)
|
||||
{
|
||||
u32 tgid = bpf_get_current_pid_tgid() >> 32;
|
||||
struct piddata piddata = {};
|
||||
|
||||
if (targ_tgid && targ_tgid != tgid)
|
||||
return 0;
|
||||
|
||||
bpf_get_current_comm(&piddata.comm, sizeof(piddata.comm));
|
||||
piddata.ts = bpf_ktime_get_ns();
|
||||
piddata.tgid = tgid;
|
||||
bpf_map_update_elem(&start, &sk, &piddata, 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int handle_tcp_rcv_state_process(void *ctx, struct sock *sk)
|
||||
{
|
||||
struct piddata *piddatap;
|
||||
struct event event = {};
|
||||
s64 delta;
|
||||
u64 ts;
|
||||
|
||||
if (BPF_CORE_READ(sk, __sk_common.skc_state) != TCP_SYN_SENT)
|
||||
return 0;
|
||||
|
||||
piddatap = bpf_map_lookup_elem(&start, &sk);
|
||||
if (!piddatap)
|
||||
return 0;
|
||||
|
||||
ts = bpf_ktime_get_ns();
|
||||
delta = (s64)(ts - piddatap->ts);
|
||||
if (delta < 0)
|
||||
goto cleanup;
|
||||
|
||||
event.delta_us = delta / 1000U;
|
||||
if (targ_min_us && event.delta_us < targ_min_us)
|
||||
goto cleanup;
|
||||
__builtin_memcpy(&event.comm, piddatap->comm,
|
||||
sizeof(event.comm));
|
||||
event.ts_us = ts / 1000;
|
||||
event.tgid = piddatap->tgid;
|
||||
event.lport = BPF_CORE_READ(sk, __sk_common.skc_num);
|
||||
event.dport = BPF_CORE_READ(sk, __sk_common.skc_dport);
|
||||
event.af = BPF_CORE_READ(sk, __sk_common.skc_family);
|
||||
if (event.af == AF_INET) {
|
||||
event.saddr_v4 = BPF_CORE_READ(sk, __sk_common.skc_rcv_saddr);
|
||||
event.daddr_v4 = BPF_CORE_READ(sk, __sk_common.skc_daddr);
|
||||
} else {
|
||||
BPF_CORE_READ_INTO(&event.saddr_v6, sk,
|
||||
__sk_common.skc_v6_rcv_saddr.in6_u.u6_addr32);
|
||||
BPF_CORE_READ_INTO(&event.daddr_v6, sk,
|
||||
__sk_common.skc_v6_daddr.in6_u.u6_addr32);
|
||||
}
|
||||
bpf_perf_event_output(ctx, &events, BPF_F_CURRENT_CPU,
|
||||
&event, sizeof(event));
|
||||
|
||||
cleanup:
|
||||
bpf_map_delete_elem(&start, &sk);
|
||||
return 0;
|
||||
}
|
||||
|
||||
SEC("kprobe/tcp_v4_connect")
|
||||
int BPF_KPROBE(tcp_v4_connect, struct sock *sk)
|
||||
{
|
||||
return trace_connect(sk);
|
||||
}
|
||||
|
||||
SEC("kprobe/tcp_v6_connect")
|
||||
int BPF_KPROBE(tcp_v6_connect, struct sock *sk)
|
||||
{
|
||||
return trace_connect(sk);
|
||||
}
|
||||
|
||||
SEC("kprobe/tcp_rcv_state_process")
|
||||
int BPF_KPROBE(tcp_rcv_state_process, struct sock *sk)
|
||||
{
|
||||
return handle_tcp_rcv_state_process(ctx, sk);
|
||||
}
|
||||
|
||||
char LICENSE[] SEC("license") = "GPL";
|
||||
26
13-tcpconnlat/tcpconnlat.bpf.h
Normal file
@@ -0,0 +1,26 @@
|
||||
/* SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) */
|
||||
#ifndef __TCPCONNLAT_H
|
||||
#define __TCPCONNLAT_H
|
||||
|
||||
#define TASK_COMM_LEN 16
|
||||
|
||||
struct event {
|
||||
// union {
|
||||
unsigned int saddr_v4;
|
||||
unsigned char saddr_v6[16];
|
||||
// };
|
||||
// union {
|
||||
unsigned int daddr_v4;
|
||||
unsigned char daddr_v6[16];
|
||||
// };
|
||||
char comm[TASK_COMM_LEN];
|
||||
unsigned long long delta_us;
|
||||
unsigned long long ts_us;
|
||||
unsigned int tgid;
|
||||
int af;
|
||||
unsigned short lport;
|
||||
unsigned short dport;
|
||||
};
|
||||
|
||||
|
||||
#endif /* __TCPCONNLAT_H_ */
|
||||
351
13-tcpconnlat/tcpconnlat.html
Normal file
BIN
13-tcpconnlat/tcpconnlat1.png
Normal file
|
After Width: | Height: | Size: 132 KiB |
BIN
13-tcpconnlat/tcpconnlat_p.png
Normal file
|
After Width: | Height: | Size: 40 KiB |
5
14-tcpstates/.gitignore
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
.vscode
|
||||
package.json
|
||||
eunomia-exporter
|
||||
ecli
|
||||
|
||||
330
14-tcpstates/index.html
Normal file
102
14-tcpstates/libbpf-bootstrap/tcpstates.bpf.c
Normal file
@@ -0,0 +1,102 @@
|
||||
// SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause)
|
||||
/* Copyright (c) 2021 Hengqi Chen */
|
||||
#include <vmlinux.h>
|
||||
#include <bpf/bpf_helpers.h>
|
||||
#include <bpf/bpf_tracing.h>
|
||||
#include <bpf/bpf_core_read.h>
|
||||
#include "tcpstates.h"
|
||||
|
||||
#define MAX_ENTRIES 10240
|
||||
#define AF_INET 2
|
||||
#define AF_INET6 10
|
||||
|
||||
const volatile bool filter_by_sport = false;
|
||||
const volatile bool filter_by_dport = false;
|
||||
const volatile short target_family = 0;
|
||||
|
||||
struct {
|
||||
__uint(type, BPF_MAP_TYPE_HASH);
|
||||
__uint(max_entries, MAX_ENTRIES);
|
||||
__type(key, __u16);
|
||||
__type(value, __u16);
|
||||
} sports SEC(".maps");
|
||||
|
||||
struct {
|
||||
__uint(type, BPF_MAP_TYPE_HASH);
|
||||
__uint(max_entries, MAX_ENTRIES);
|
||||
__type(key, __u16);
|
||||
__type(value, __u16);
|
||||
} dports SEC(".maps");
|
||||
|
||||
struct {
|
||||
__uint(type, BPF_MAP_TYPE_HASH);
|
||||
__uint(max_entries, MAX_ENTRIES);
|
||||
__type(key, struct sock *);
|
||||
__type(value, __u64);
|
||||
} timestamps SEC(".maps");
|
||||
|
||||
struct {
|
||||
__uint(type, BPF_MAP_TYPE_PERF_EVENT_ARRAY);
|
||||
__uint(key_size, sizeof(__u32));
|
||||
__uint(value_size, sizeof(__u32));
|
||||
} events SEC(".maps");
|
||||
|
||||
SEC("tracepoint/sock/inet_sock_set_state")
|
||||
int handle_set_state(struct trace_event_raw_inet_sock_set_state *ctx)
|
||||
{
|
||||
struct sock *sk = (struct sock *)ctx->skaddr;
|
||||
__u16 family = ctx->family;
|
||||
__u16 sport = ctx->sport;
|
||||
__u16 dport = ctx->dport;
|
||||
__u64 *tsp, delta_us, ts;
|
||||
struct event event = {};
|
||||
|
||||
if (ctx->protocol != IPPROTO_TCP)
|
||||
return 0;
|
||||
|
||||
if (target_family && target_family != family)
|
||||
return 0;
|
||||
|
||||
if (filter_by_sport && !bpf_map_lookup_elem(&sports, &sport))
|
||||
return 0;
|
||||
|
||||
if (filter_by_dport && !bpf_map_lookup_elem(&dports, &dport))
|
||||
return 0;
|
||||
|
||||
tsp = bpf_map_lookup_elem(×tamps, &sk);
|
||||
ts = bpf_ktime_get_ns();
|
||||
if (!tsp)
|
||||
delta_us = 0;
|
||||
else
|
||||
delta_us = (ts - *tsp) / 1000;
|
||||
|
||||
event.skaddr = (__u64)sk;
|
||||
event.ts_us = ts / 1000;
|
||||
event.delta_us = delta_us;
|
||||
event.pid = bpf_get_current_pid_tgid() >> 32;
|
||||
event.oldstate = ctx->oldstate;
|
||||
event.newstate = ctx->newstate;
|
||||
event.family = family;
|
||||
event.sport = sport;
|
||||
event.dport = dport;
|
||||
bpf_get_current_comm(&event.task, sizeof(event.task));
|
||||
|
||||
if (family == AF_INET) {
|
||||
bpf_probe_read_kernel(&event.saddr, sizeof(event.saddr), &sk->__sk_common.skc_rcv_saddr);
|
||||
bpf_probe_read_kernel(&event.daddr, sizeof(event.daddr), &sk->__sk_common.skc_daddr);
|
||||
} else { /* family == AF_INET6 */
|
||||
bpf_probe_read_kernel(&event.saddr, sizeof(event.saddr), &sk->__sk_common.skc_v6_rcv_saddr.in6_u.u6_addr32);
|
||||
bpf_probe_read_kernel(&event.daddr, sizeof(event.daddr), &sk->__sk_common.skc_v6_daddr.in6_u.u6_addr32);
|
||||
}
|
||||
|
||||
bpf_perf_event_output(ctx, &events, BPF_F_CURRENT_CPU, &event, sizeof(event));
|
||||
|
||||
if (ctx->newstate == TCP_CLOSE)
|
||||
bpf_map_delete_elem(×tamps, &sk);
|
||||
else
|
||||
bpf_map_update_elem(×tamps, &sk, &ts, BPF_ANY);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
char LICENSE[] SEC("license") = "Dual BSD/GPL";
|
||||
546
14-tcpstates/libbpf-bootstrap/tcpstates.c
Normal file
@@ -0,0 +1,546 @@
|
||||
// SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause)
|
||||
|
||||
/*
|
||||
* tcpstates Trace TCP session state changes with durations.
|
||||
* Copyright (c) 2021 Hengqi Chen
|
||||
*
|
||||
* Based on tcpstates(8) from BCC by Brendan Gregg.
|
||||
* 18-Dec-2021 Hengqi Chen Created this.
|
||||
*/
|
||||
#include <argp.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <bpf/bpf.h>
|
||||
#include <bpf/libbpf.h>
|
||||
#include <errno.h>
|
||||
#include <signal.h>
|
||||
#include <string.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/utsname.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
#include <zlib.h>
|
||||
// #include "btf_helpers.h"
|
||||
#include "tcpstates.h"
|
||||
#include "tcpstates.skel.h"
|
||||
// #include "trace_helpers.h"
|
||||
|
||||
#define PERF_BUFFER_PAGES 16
|
||||
#define PERF_POLL_TIMEOUT_MS 100
|
||||
#define warn(...) fprintf(stderr, __VA_ARGS__)
|
||||
|
||||
static volatile sig_atomic_t exiting = 0;
|
||||
|
||||
static bool emit_timestamp = false;
|
||||
static short target_family = 0;
|
||||
static char* target_sports = NULL;
|
||||
static char* target_dports = NULL;
|
||||
static bool wide_output = false;
|
||||
static bool verbose = false;
|
||||
static const char* tcp_states[] = {
|
||||
[1] = "ESTABLISHED", [2] = "SYN_SENT", [3] = "SYN_RECV",
|
||||
[4] = "FIN_WAIT1", [5] = "FIN_WAIT2", [6] = "TIME_WAIT",
|
||||
[7] = "CLOSE", [8] = "CLOSE_WAIT", [9] = "LAST_ACK",
|
||||
[10] = "LISTEN", [11] = "CLOSING", [12] = "NEW_SYN_RECV",
|
||||
[13] = "UNKNOWN",
|
||||
};
|
||||
|
||||
const char* argp_program_version = "tcpstates 1.0";
|
||||
const char* argp_program_bug_address =
|
||||
"https://github.com/iovisor/bcc/tree/master/libbpf-tools";
|
||||
const char argp_program_doc[] =
|
||||
"Trace TCP session state changes and durations.\n"
|
||||
"\n"
|
||||
"USAGE: tcpstates [-4] [-6] [-T] [-L lport] [-D dport]\n"
|
||||
"\n"
|
||||
"EXAMPLES:\n"
|
||||
" tcpstates # trace all TCP state changes\n"
|
||||
" tcpstates -T # include timestamps\n"
|
||||
" tcpstates -L 80 # only trace local port 80\n"
|
||||
" tcpstates -D 80 # only trace remote port 80\n";
|
||||
|
||||
static const struct argp_option opts[] = {
|
||||
{"verbose", 'v', NULL, 0, "Verbose debug output"},
|
||||
{"timestamp", 'T', NULL, 0, "Include timestamp on output"},
|
||||
{"ipv4", '4', NULL, 0, "Trace IPv4 family only"},
|
||||
{"ipv6", '6', NULL, 0, "Trace IPv6 family only"},
|
||||
{"wide", 'w', NULL, 0, "Wide column output (fits IPv6 addresses)"},
|
||||
{"localport", 'L', "LPORT", 0,
|
||||
"Comma-separated list of local ports to trace."},
|
||||
{"remoteport", 'D', "DPORT", 0,
|
||||
"Comma-separated list of remote ports to trace."},
|
||||
{NULL, 'h', NULL, OPTION_HIDDEN, "Show the full help"},
|
||||
{},
|
||||
};
|
||||
|
||||
static error_t parse_arg(int key, char* arg, struct argp_state* state) {
|
||||
long port_num;
|
||||
char* port;
|
||||
|
||||
switch (key) {
|
||||
case 'v':
|
||||
verbose = true;
|
||||
break;
|
||||
case 'T':
|
||||
emit_timestamp = true;
|
||||
break;
|
||||
case '4':
|
||||
target_family = AF_INET;
|
||||
break;
|
||||
case '6':
|
||||
target_family = AF_INET6;
|
||||
break;
|
||||
case 'w':
|
||||
wide_output = true;
|
||||
break;
|
||||
case 'L':
|
||||
if (!arg) {
|
||||
warn("No ports specified\n");
|
||||
argp_usage(state);
|
||||
}
|
||||
target_sports = strdup(arg);
|
||||
port = strtok(arg, ",");
|
||||
while (port) {
|
||||
port_num = strtol(port, NULL, 10);
|
||||
if (errno || port_num <= 0 || port_num > 65536) {
|
||||
warn("Invalid ports: %s\n", arg);
|
||||
argp_usage(state);
|
||||
}
|
||||
port = strtok(NULL, ",");
|
||||
}
|
||||
break;
|
||||
case 'D':
|
||||
if (!arg) {
|
||||
warn("No ports specified\n");
|
||||
argp_usage(state);
|
||||
}
|
||||
target_dports = strdup(arg);
|
||||
port = strtok(arg, ",");
|
||||
while (port) {
|
||||
port_num = strtol(port, NULL, 10);
|
||||
if (errno || port_num <= 0 || port_num > 65536) {
|
||||
warn("Invalid ports: %s\n", arg);
|
||||
argp_usage(state);
|
||||
}
|
||||
port = strtok(NULL, ",");
|
||||
}
|
||||
break;
|
||||
case 'h':
|
||||
argp_state_help(state, stderr, ARGP_HELP_STD_HELP);
|
||||
break;
|
||||
default:
|
||||
return ARGP_ERR_UNKNOWN;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int libbpf_print_fn(enum libbpf_print_level level,
|
||||
const char* format,
|
||||
va_list args) {
|
||||
if (level == LIBBPF_DEBUG && !verbose)
|
||||
return 0;
|
||||
|
||||
return vfprintf(stderr, format, args);
|
||||
}
|
||||
|
||||
static void sig_int(int signo) {
|
||||
exiting = 1;
|
||||
}
|
||||
|
||||
static void handle_event(void* ctx, int cpu, void* data, __u32 data_sz) {
|
||||
char ts[32], saddr[26], daddr[26];
|
||||
struct event* e = data;
|
||||
struct tm* tm;
|
||||
int family;
|
||||
time_t t;
|
||||
|
||||
if (emit_timestamp) {
|
||||
time(&t);
|
||||
tm = localtime(&t);
|
||||
strftime(ts, sizeof(ts), "%H:%M:%S", tm);
|
||||
printf("%8s ", ts);
|
||||
}
|
||||
|
||||
inet_ntop(e->family, &e->saddr, saddr, sizeof(saddr));
|
||||
inet_ntop(e->family, &e->daddr, daddr, sizeof(daddr));
|
||||
if (wide_output) {
|
||||
family = e->family == AF_INET ? 4 : 6;
|
||||
printf(
|
||||
"%-16llx %-7d %-16s %-2d %-26s %-5d %-26s %-5d %-11s -> %-11s "
|
||||
"%.3f\n",
|
||||
e->skaddr, e->pid, e->task, family, saddr, e->sport, daddr,
|
||||
e->dport, tcp_states[e->oldstate], tcp_states[e->newstate],
|
||||
(double)e->delta_us / 1000);
|
||||
} else {
|
||||
printf(
|
||||
"%-16llx %-7d %-10.10s %-15s %-5d %-15s %-5d %-11s -> %-11s %.3f\n",
|
||||
e->skaddr, e->pid, e->task, saddr, e->sport, daddr, e->dport,
|
||||
tcp_states[e->oldstate], tcp_states[e->newstate],
|
||||
(double)e->delta_us / 1000);
|
||||
}
|
||||
}
|
||||
|
||||
static void handle_lost_events(void* ctx, int cpu, __u64 lost_cnt) {
|
||||
warn("lost %llu events on CPU #%d\n", lost_cnt, cpu);
|
||||
}
|
||||
|
||||
extern unsigned char _binary_min_core_btfs_tar_gz_start[] __attribute__((weak));
|
||||
extern unsigned char _binary_min_core_btfs_tar_gz_end[] __attribute__((weak));
|
||||
|
||||
|
||||
/* tar header from
|
||||
* https://github.com/tklauser/libtar/blob/v1.2.20/lib/libtar.h#L39-L60 */
|
||||
struct tar_header {
|
||||
char name[100];
|
||||
char mode[8];
|
||||
char uid[8];
|
||||
char gid[8];
|
||||
char size[12];
|
||||
char mtime[12];
|
||||
char chksum[8];
|
||||
char typeflag;
|
||||
char linkname[100];
|
||||
char magic[6];
|
||||
char version[2];
|
||||
char uname[32];
|
||||
char gname[32];
|
||||
char devmajor[8];
|
||||
char devminor[8];
|
||||
char prefix[155];
|
||||
char padding[12];
|
||||
};
|
||||
|
||||
static char* tar_file_start(struct tar_header* tar,
|
||||
const char* name,
|
||||
int* length) {
|
||||
while (tar->name[0]) {
|
||||
sscanf(tar->size, "%o", length);
|
||||
if (!strcmp(tar->name, name))
|
||||
return (char*)(tar + 1);
|
||||
tar += 1 + (*length + 511) / 512;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
#define FIELD_LEN 65
|
||||
#define ID_FMT "ID=%64s"
|
||||
#define VERSION_FMT "VERSION_ID=\"%64s"
|
||||
|
||||
struct os_info {
|
||||
char id[FIELD_LEN];
|
||||
char version[FIELD_LEN];
|
||||
char arch[FIELD_LEN];
|
||||
char kernel_release[FIELD_LEN];
|
||||
};
|
||||
|
||||
static struct os_info* get_os_info() {
|
||||
struct os_info* info = NULL;
|
||||
struct utsname u;
|
||||
size_t len = 0;
|
||||
ssize_t read;
|
||||
char* line = NULL;
|
||||
FILE* f;
|
||||
|
||||
if (uname(&u) == -1)
|
||||
return NULL;
|
||||
|
||||
f = fopen("/etc/os-release", "r");
|
||||
if (!f)
|
||||
return NULL;
|
||||
|
||||
info = calloc(1, sizeof(*info));
|
||||
if (!info)
|
||||
goto out;
|
||||
|
||||
strncpy(info->kernel_release, u.release, FIELD_LEN);
|
||||
strncpy(info->arch, u.machine, FIELD_LEN);
|
||||
|
||||
while ((read = getline(&line, &len, f)) != -1) {
|
||||
if (sscanf(line, ID_FMT, info->id) == 1)
|
||||
continue;
|
||||
|
||||
if (sscanf(line, VERSION_FMT, info->version) == 1) {
|
||||
/* remove '"' suffix */
|
||||
info->version[strlen(info->version) - 1] = 0;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
out:
|
||||
free(line);
|
||||
fclose(f);
|
||||
|
||||
return info;
|
||||
}
|
||||
#define INITIAL_BUF_SIZE (1024 * 1024 * 4) /* 4MB */
|
||||
|
||||
/* adapted from https://zlib.net/zlib_how.html */
|
||||
static int inflate_gz(unsigned char* src,
|
||||
int src_size,
|
||||
unsigned char** dst,
|
||||
int* dst_size) {
|
||||
size_t size = INITIAL_BUF_SIZE;
|
||||
size_t next_size = size;
|
||||
z_stream strm;
|
||||
void* tmp;
|
||||
int ret;
|
||||
|
||||
strm.zalloc = Z_NULL;
|
||||
strm.zfree = Z_NULL;
|
||||
strm.opaque = Z_NULL;
|
||||
strm.avail_in = 0;
|
||||
strm.next_in = Z_NULL;
|
||||
|
||||
ret = inflateInit2(&strm, 16 + MAX_WBITS);
|
||||
if (ret != Z_OK)
|
||||
return -EINVAL;
|
||||
|
||||
*dst = malloc(size);
|
||||
if (!*dst)
|
||||
return -ENOMEM;
|
||||
|
||||
strm.next_in = src;
|
||||
strm.avail_in = src_size;
|
||||
|
||||
/* run inflate() on input until it returns Z_STREAM_END */
|
||||
do {
|
||||
strm.next_out = *dst + strm.total_out;
|
||||
strm.avail_out = next_size;
|
||||
ret = inflate(&strm, Z_NO_FLUSH);
|
||||
if (ret != Z_OK && ret != Z_STREAM_END)
|
||||
goto out_err;
|
||||
/* we need more space */
|
||||
if (strm.avail_out == 0) {
|
||||
next_size = size;
|
||||
size *= 2;
|
||||
tmp = realloc(*dst, size);
|
||||
if (!tmp) {
|
||||
ret = -ENOMEM;
|
||||
goto out_err;
|
||||
}
|
||||
*dst = tmp;
|
||||
}
|
||||
} while (ret != Z_STREAM_END);
|
||||
|
||||
*dst_size = strm.total_out;
|
||||
|
||||
/* clean up and return */
|
||||
ret = inflateEnd(&strm);
|
||||
if (ret != Z_OK) {
|
||||
ret = -EINVAL;
|
||||
goto out_err;
|
||||
}
|
||||
return 0;
|
||||
|
||||
out_err:
|
||||
free(*dst);
|
||||
*dst = NULL;
|
||||
return ret;
|
||||
}
|
||||
struct btf *btf__load_vmlinux_btf(void);
|
||||
void btf__free(struct btf *btf);
|
||||
static bool vmlinux_btf_exists(void) {
|
||||
struct btf* btf;
|
||||
int err;
|
||||
|
||||
btf = btf__load_vmlinux_btf();
|
||||
err = libbpf_get_error(btf);
|
||||
if (err)
|
||||
return false;
|
||||
|
||||
btf__free(btf);
|
||||
return true;
|
||||
}
|
||||
|
||||
static int ensure_core_btf(struct bpf_object_open_opts* opts) {
|
||||
char name_fmt[] = "./%s/%s/%s/%s.btf";
|
||||
char btf_path[] = "/tmp/bcc-libbpf-tools.btf.XXXXXX";
|
||||
struct os_info* info = NULL;
|
||||
unsigned char* dst_buf = NULL;
|
||||
char* file_start;
|
||||
int dst_size = 0;
|
||||
char name[100];
|
||||
FILE* dst = NULL;
|
||||
int ret;
|
||||
|
||||
/* do nothing if the system provides BTF */
|
||||
if (vmlinux_btf_exists())
|
||||
return 0;
|
||||
|
||||
/* compiled without min core btfs */
|
||||
if (!_binary_min_core_btfs_tar_gz_start)
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
info = get_os_info();
|
||||
if (!info)
|
||||
return -errno;
|
||||
|
||||
ret = mkstemp(btf_path);
|
||||
if (ret < 0) {
|
||||
ret = -errno;
|
||||
goto out;
|
||||
}
|
||||
|
||||
dst = fdopen(ret, "wb");
|
||||
if (!dst) {
|
||||
ret = -errno;
|
||||
goto out;
|
||||
}
|
||||
|
||||
ret = snprintf(name, sizeof(name), name_fmt, info->id, info->version,
|
||||
info->arch, info->kernel_release);
|
||||
if (ret < 0 || ret == sizeof(name)) {
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
ret = inflate_gz(
|
||||
_binary_min_core_btfs_tar_gz_start,
|
||||
_binary_min_core_btfs_tar_gz_end - _binary_min_core_btfs_tar_gz_start,
|
||||
&dst_buf, &dst_size);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
|
||||
ret = 0;
|
||||
file_start = tar_file_start((struct tar_header*)dst_buf, name, &dst_size);
|
||||
if (!file_start) {
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (fwrite(file_start, 1, dst_size, dst) != dst_size) {
|
||||
ret = -ferror(dst);
|
||||
goto out;
|
||||
}
|
||||
|
||||
opts->btf_custom_path = strdup(btf_path);
|
||||
if (!opts->btf_custom_path)
|
||||
ret = -ENOMEM;
|
||||
|
||||
out:
|
||||
free(info);
|
||||
fclose(dst);
|
||||
free(dst_buf);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void cleanup_core_btf(struct bpf_object_open_opts* opts) {
|
||||
if (!opts)
|
||||
return;
|
||||
|
||||
if (!opts->btf_custom_path)
|
||||
return;
|
||||
|
||||
unlink(opts->btf_custom_path);
|
||||
free((void*)opts->btf_custom_path);
|
||||
}
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
LIBBPF_OPTS(bpf_object_open_opts, open_opts);
|
||||
static const struct argp argp = {
|
||||
.options = opts,
|
||||
.parser = parse_arg,
|
||||
.doc = argp_program_doc,
|
||||
};
|
||||
struct perf_buffer* pb = NULL;
|
||||
struct tcpstates_bpf* obj;
|
||||
int err, port_map_fd;
|
||||
short port_num;
|
||||
char* port;
|
||||
|
||||
err = argp_parse(&argp, argc, argv, 0, NULL, NULL);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
libbpf_set_strict_mode(LIBBPF_STRICT_ALL);
|
||||
libbpf_set_print(libbpf_print_fn);
|
||||
|
||||
err = ensure_core_btf(&open_opts);
|
||||
if (err) {
|
||||
warn("failed to fetch necessary BTF for CO-RE: %s\n", strerror(-err));
|
||||
return 1;
|
||||
}
|
||||
|
||||
obj = tcpstates_bpf__open_opts(&open_opts);
|
||||
if (!obj) {
|
||||
warn("failed to open BPF object\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
obj->rodata->filter_by_sport = target_sports != NULL;
|
||||
obj->rodata->filter_by_dport = target_dports != NULL;
|
||||
obj->rodata->target_family = target_family;
|
||||
|
||||
err = tcpstates_bpf__load(obj);
|
||||
if (err) {
|
||||
warn("failed to load BPF object: %d\n", err);
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if (target_sports) {
|
||||
port_map_fd = bpf_map__fd(obj->maps.sports);
|
||||
port = strtok(target_sports, ",");
|
||||
while (port) {
|
||||
port_num = strtol(port, NULL, 10);
|
||||
bpf_map_update_elem(port_map_fd, &port_num, &port_num, BPF_ANY);
|
||||
port = strtok(NULL, ",");
|
||||
}
|
||||
}
|
||||
if (target_dports) {
|
||||
port_map_fd = bpf_map__fd(obj->maps.dports);
|
||||
port = strtok(target_dports, ",");
|
||||
while (port) {
|
||||
port_num = strtol(port, NULL, 10);
|
||||
bpf_map_update_elem(port_map_fd, &port_num, &port_num, BPF_ANY);
|
||||
port = strtok(NULL, ",");
|
||||
}
|
||||
}
|
||||
|
||||
err = tcpstates_bpf__attach(obj);
|
||||
if (err) {
|
||||
warn("failed to attach BPF programs: %d\n", err);
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
pb = perf_buffer__new(bpf_map__fd(obj->maps.events), PERF_BUFFER_PAGES,
|
||||
handle_event, handle_lost_events, NULL, NULL);
|
||||
if (!pb) {
|
||||
err = -errno;
|
||||
warn("failed to open perf buffer: %d\n", err);
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if (signal(SIGINT, sig_int) == SIG_ERR) {
|
||||
warn("can't set signal handler: %s\n", strerror(errno));
|
||||
err = 1;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if (emit_timestamp)
|
||||
printf("%-8s ", "TIME(s)");
|
||||
if (wide_output)
|
||||
printf(
|
||||
"%-16s %-7s %-16s %-2s %-26s %-5s %-26s %-5s %-11s -> %-11s %s\n",
|
||||
"SKADDR", "PID", "COMM", "IP", "LADDR", "LPORT", "RADDR", "RPORT",
|
||||
"OLDSTATE", "NEWSTATE", "MS");
|
||||
else
|
||||
printf("%-16s %-7s %-10s %-15s %-5s %-15s %-5s %-11s -> %-11s %s\n",
|
||||
"SKADDR", "PID", "COMM", "LADDR", "LPORT", "RADDR", "RPORT",
|
||||
"OLDSTATE", "NEWSTATE", "MS");
|
||||
|
||||
while (!exiting) {
|
||||
err = perf_buffer__poll(pb, PERF_POLL_TIMEOUT_MS);
|
||||
if (err < 0 && err != -EINTR) {
|
||||
warn("error polling perf buffer: %s\n", strerror(-err));
|
||||
goto cleanup;
|
||||
}
|
||||
/* reset err to return 0 if exiting */
|
||||
err = 0;
|
||||
}
|
||||
|
||||
cleanup:
|
||||
perf_buffer__free(pb);
|
||||
tcpstates_bpf__destroy(obj);
|
||||
cleanup_core_btf(&open_opts);
|
||||
|
||||
return err != 0;
|
||||
}
|
||||
23
14-tcpstates/libbpf-bootstrap/tcpstates.h
Normal file
@@ -0,0 +1,23 @@
|
||||
// SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause)
|
||||
/* Copyright (c) 2021 Hengqi Chen */
|
||||
#ifndef __TCPSTATES_H
|
||||
#define __TCPSTATES_H
|
||||
|
||||
#define TASK_COMM_LEN 16
|
||||
|
||||
struct event {
|
||||
unsigned __int128 saddr;
|
||||
unsigned __int128 daddr;
|
||||
__u64 skaddr;
|
||||
__u64 ts_us;
|
||||
__u64 delta_us;
|
||||
__u32 pid;
|
||||
int oldstate;
|
||||
int newstate;
|
||||
__u16 family;
|
||||
__u16 sport;
|
||||
__u16 dport;
|
||||
char task[TASK_COMM_LEN];
|
||||
};
|
||||
|
||||
#endif /* __TCPSTATES_H */
|
||||
109
14-tcpstates/tcpstates.bpf.c
Normal file
@@ -0,0 +1,109 @@
|
||||
// SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause)
|
||||
/* Copyright (c) 2021 Hengqi Chen */
|
||||
#include <vmlinux.h>
|
||||
#include <bpf/bpf_helpers.h>
|
||||
#include <bpf/bpf_tracing.h>
|
||||
#include <bpf/bpf_core_read.h>
|
||||
#include "tcpstates.bpf.h"
|
||||
|
||||
#define MAX_ENTRIES 10240
|
||||
#define AF_INET 2
|
||||
#define AF_INET6 10
|
||||
|
||||
const volatile bool filter_by_sport = false;
|
||||
const volatile bool filter_by_dport = false;
|
||||
const volatile short target_family = 0;
|
||||
|
||||
struct
|
||||
{
|
||||
__uint(type, BPF_MAP_TYPE_HASH);
|
||||
__uint(max_entries, MAX_ENTRIES);
|
||||
__type(key, __u16);
|
||||
__type(value, __u16);
|
||||
} sports SEC(".maps");
|
||||
|
||||
struct
|
||||
{
|
||||
__uint(type, BPF_MAP_TYPE_HASH);
|
||||
__uint(max_entries, MAX_ENTRIES);
|
||||
__type(key, __u16);
|
||||
__type(value, __u16);
|
||||
} dports SEC(".maps");
|
||||
|
||||
struct
|
||||
{
|
||||
__uint(type, BPF_MAP_TYPE_HASH);
|
||||
__uint(max_entries, MAX_ENTRIES);
|
||||
__type(key, struct sock *);
|
||||
__type(value, __u64);
|
||||
} timestamps SEC(".maps");
|
||||
|
||||
struct
|
||||
{
|
||||
__uint(type, BPF_MAP_TYPE_PERF_EVENT_ARRAY);
|
||||
__uint(key_size, sizeof(__u32));
|
||||
__uint(value_size, sizeof(__u32));
|
||||
} events SEC(".maps");
|
||||
|
||||
SEC("tracepoint/sock/inet_sock_set_state")
|
||||
int handle_set_state(struct trace_event_raw_inet_sock_set_state *ctx)
|
||||
{
|
||||
struct sock *sk = (struct sock *)ctx->skaddr;
|
||||
__u16 family = ctx->family;
|
||||
__u16 sport = ctx->sport;
|
||||
__u16 dport = ctx->dport;
|
||||
__u64 *tsp, delta_us, ts;
|
||||
struct event event = {};
|
||||
|
||||
if (ctx->protocol != IPPROTO_TCP)
|
||||
return 0;
|
||||
|
||||
if (target_family && target_family != family)
|
||||
return 0;
|
||||
|
||||
if (filter_by_sport && !bpf_map_lookup_elem(&sports, &sport))
|
||||
return 0;
|
||||
|
||||
if (filter_by_dport && !bpf_map_lookup_elem(&dports, &dport))
|
||||
return 0;
|
||||
|
||||
tsp = bpf_map_lookup_elem(×tamps, &sk);
|
||||
ts = bpf_ktime_get_ns();
|
||||
if (!tsp)
|
||||
delta_us = 0;
|
||||
else
|
||||
delta_us = (ts - *tsp) / 1000;
|
||||
|
||||
event.skaddr = (__u64)sk;
|
||||
event.ts_us = ts / 1000;
|
||||
event.delta_us = delta_us;
|
||||
event.pid = bpf_get_current_pid_tgid() >> 32;
|
||||
event.oldstate = ctx->oldstate;
|
||||
event.newstate = ctx->newstate;
|
||||
event.family = family;
|
||||
event.sport = sport;
|
||||
event.dport = dport;
|
||||
bpf_get_current_comm(&event.task, sizeof(event.task));
|
||||
|
||||
if (family == AF_INET)
|
||||
{
|
||||
bpf_probe_read_kernel(&event.saddr, sizeof(event.saddr), &sk->__sk_common.skc_rcv_saddr);
|
||||
bpf_probe_read_kernel(&event.daddr, sizeof(event.daddr), &sk->__sk_common.skc_daddr);
|
||||
}
|
||||
else
|
||||
{ /* family == AF_INET6 */
|
||||
bpf_probe_read_kernel(&event.saddr, sizeof(event.saddr), &sk->__sk_common.skc_v6_rcv_saddr.in6_u.u6_addr32);
|
||||
bpf_probe_read_kernel(&event.daddr, sizeof(event.daddr), &sk->__sk_common.skc_v6_daddr.in6_u.u6_addr32);
|
||||
}
|
||||
|
||||
bpf_perf_event_output(ctx, &events, BPF_F_CURRENT_CPU, &event, sizeof(event));
|
||||
|
||||
if (ctx->newstate == TCP_CLOSE)
|
||||
bpf_map_delete_elem(×tamps, &sk);
|
||||
else
|
||||
bpf_map_update_elem(×tamps, &sk, &ts, BPF_ANY);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
char LICENSE[] SEC("license") = "Dual BSD/GPL";
|
||||
24
14-tcpstates/tcpstates.bpf.h
Normal file
@@ -0,0 +1,24 @@
|
||||
// SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause)
|
||||
/* Copyright (c) 2021 Hengqi Chen */
|
||||
#ifndef __TCPSTATES_H
|
||||
#define __TCPSTATES_H
|
||||
|
||||
#define TASK_COMM_LEN 16
|
||||
|
||||
struct event
|
||||
{
|
||||
unsigned __int128 saddr;
|
||||
unsigned __int128 daddr;
|
||||
__u64 skaddr;
|
||||
__u64 ts_us;
|
||||
__u64 delta_us;
|
||||
__u32 pid;
|
||||
int oldstate;
|
||||
int newstate;
|
||||
__u16 family;
|
||||
__u16 sport;
|
||||
__u16 dport;
|
||||
char task[TASK_COMM_LEN];
|
||||
};
|
||||
|
||||
#endif /* __TCPSTATES_H */
|
||||
212
15-tcprtt/index.html
Normal file
215
16-memleak/index.html
Normal file
211
17-biopattern/index.html
Normal file
200
18-further-reading/index.html
Normal file
6
19-lsm-connect/.gitignore
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
.vscode
|
||||
package.json
|
||||
*.o
|
||||
*.skel.json
|
||||
*.skel.yaml
|
||||
package.yaml
|
||||
219
19-lsm-connect/index.html
Normal file
41
19-lsm-connect/lsm-connect.bpf.c
Normal file
@@ -0,0 +1,41 @@
|
||||
#include "vmlinux.h"
|
||||
#include <bpf/bpf_core_read.h>
|
||||
#include <bpf/bpf_helpers.h>
|
||||
#include <bpf/bpf_tracing.h>
|
||||
|
||||
char LICENSE[] SEC("license") = "GPL";
|
||||
|
||||
#define EPERM 1
|
||||
#define AF_INET 2
|
||||
|
||||
const __u32 blockme = 16843009; // 1.1.1.1 -> int
|
||||
|
||||
SEC("lsm/socket_connect")
|
||||
int BPF_PROG(restrict_connect, struct socket *sock, struct sockaddr *address, int addrlen, int ret)
|
||||
{
|
||||
// Satisfying "cannot override a denial" rule
|
||||
if (ret != 0)
|
||||
{
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Only IPv4 in this example
|
||||
if (address->sa_family != AF_INET)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Cast the address to an IPv4 socket address
|
||||
struct sockaddr_in *addr = (struct sockaddr_in *)address;
|
||||
|
||||
// Where do you want to go?
|
||||
__u32 dest = addr->sin_addr.s_addr;
|
||||
bpf_printk("lsm: found connect to %d", dest);
|
||||
|
||||
if (dest == blockme)
|
||||
{
|
||||
bpf_printk("lsm: blocking %d", dest);
|
||||
return -EPERM;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
7
2-kprobe-unlink/.gitignore
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
.vscode
|
||||
package.json
|
||||
*.o
|
||||
*.skel.json
|
||||
*.skel.yaml
|
||||
package.yaml
|
||||
ecli
|
||||
276
2-kprobe-unlink/index.html
Normal file
30
2-kprobe-unlink/kprobe-link.bpf.c
Normal file
@@ -0,0 +1,30 @@
|
||||
// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
|
||||
/* Copyright (c) 2021 Sartura */
|
||||
#include "vmlinux.h"
|
||||
#include <bpf/bpf_helpers.h>
|
||||
#include <bpf/bpf_tracing.h>
|
||||
#include <bpf/bpf_core_read.h>
|
||||
|
||||
char LICENSE[] SEC("license") = "Dual BSD/GPL";
|
||||
|
||||
SEC("kprobe/do_unlinkat")
|
||||
int BPF_KPROBE(do_unlinkat, int dfd, struct filename *name)
|
||||
{
|
||||
pid_t pid;
|
||||
const char *filename;
|
||||
|
||||
pid = bpf_get_current_pid_tgid() >> 32;
|
||||
filename = BPF_CORE_READ(name, name);
|
||||
bpf_printk("KPROBE ENTRY pid = %d, filename = %s\n", pid, filename);
|
||||
return 0;
|
||||
}
|
||||
|
||||
SEC("kretprobe/do_unlinkat")
|
||||
int BPF_KRETPROBE(do_unlinkat_exit, long ret)
|
||||
{
|
||||
pid_t pid;
|
||||
|
||||
pid = bpf_get_current_pid_tgid() >> 32;
|
||||
bpf_printk("KPROBE EXIT: pid = %d, ret = %ld\n", pid, ret);
|
||||
return 0;
|
||||
}
|
||||
10
20-tc/.gitignore
vendored
Executable file
@@ -0,0 +1,10 @@
|
||||
.vscode
|
||||
package.json
|
||||
*.wasm
|
||||
ewasm-skel.h
|
||||
ecli
|
||||
ewasm
|
||||
*.o
|
||||
*.skel.json
|
||||
*.skel.yaml
|
||||
package.yaml
|
||||
265
20-tc/index.html
Normal file
36
20-tc/tc.bpf.c
Normal file
@@ -0,0 +1,36 @@
|
||||
// SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause)
|
||||
/* Copyright (c) 2022 Hengqi Chen */
|
||||
#include <vmlinux.h>
|
||||
#include <bpf/bpf_endian.h>
|
||||
#include <bpf/bpf_helpers.h>
|
||||
#include <bpf/bpf_tracing.h>
|
||||
|
||||
#define TC_ACT_OK 0
|
||||
#define ETH_P_IP 0x0800 /* Internet Protocol packet */
|
||||
|
||||
/// @tchook {"ifindex":1, "attach_point":"BPF_TC_INGRESS"}
|
||||
/// @tcopts {"handle":1, "priority":1}
|
||||
SEC("tc")
|
||||
int tc_ingress(struct __sk_buff *ctx)
|
||||
{
|
||||
void *data_end = (void *)(__u64)ctx->data_end;
|
||||
void *data = (void *)(__u64)ctx->data;
|
||||
struct ethhdr *l2;
|
||||
struct iphdr *l3;
|
||||
|
||||
if (ctx->protocol != bpf_htons(ETH_P_IP))
|
||||
return TC_ACT_OK;
|
||||
|
||||
l2 = data;
|
||||
if ((void *)(l2 + 1) > data_end)
|
||||
return TC_ACT_OK;
|
||||
|
||||
l3 = (struct iphdr *)(l2 + 1);
|
||||
if ((void *)(l3 + 1) > data_end)
|
||||
return TC_ACT_OK;
|
||||
|
||||
bpf_printk("Got IP packet: tot_len: %d, ttl: %d", bpf_ntohs(l3->tot_len), l3->ttl);
|
||||
return TC_ACT_OK;
|
||||
}
|
||||
|
||||
char __license[] SEC("license") = "GPL";
|
||||
7
3-fentry-unlink/.gitignore
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
.vscode
|
||||
package.json
|
||||
*.o
|
||||
*.skel.json
|
||||
*.skel.yaml
|
||||
package.yaml
|
||||
ecli
|
||||
27
3-fentry-unlink/fentry-link.bpf.c
Normal file
@@ -0,0 +1,27 @@
|
||||
// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
|
||||
/* Copyright (c) 2021 Sartura */
|
||||
#include "vmlinux.h"
|
||||
#include <bpf/bpf_helpers.h>
|
||||
#include <bpf/bpf_tracing.h>
|
||||
|
||||
char LICENSE[] SEC("license") = "Dual BSD/GPL";
|
||||
|
||||
SEC("fentry/do_unlinkat")
|
||||
int BPF_PROG(do_unlinkat, int dfd, struct filename *name)
|
||||
{
|
||||
pid_t pid;
|
||||
|
||||
pid = bpf_get_current_pid_tgid() >> 32;
|
||||
bpf_printk("fentry: pid = %d, filename = %s\n", pid, name->name);
|
||||
return 0;
|
||||
}
|
||||
|
||||
SEC("fexit/do_unlinkat")
|
||||
int BPF_PROG(do_unlinkat_exit, int dfd, struct filename *name, long ret)
|
||||
{
|
||||
pid_t pid;
|
||||
|
||||
pid = bpf_get_current_pid_tgid() >> 32;
|
||||
bpf_printk("fexit: pid = %d, filename = %s, ret = %ld\n", pid, name->name, ret);
|
||||
return 0;
|
||||
}
|
||||
256
3-fentry-unlink/index.html
Normal file
7
4-opensnoop/.gitignore
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
.vscode
|
||||
package.json
|
||||
eunomia-exporter
|
||||
ecli
|
||||
*.bpf.o
|
||||
*.skel.json
|
||||
*.skel.yaml
|
||||
271
4-opensnoop/index.html
Normal file
21
4-opensnoop/opensnoop.bpf.c
Normal file
@@ -0,0 +1,21 @@
|
||||
#include <vmlinux.h>
|
||||
#include <bpf/bpf_helpers.h>
|
||||
|
||||
/// @description "Process ID to trace"
|
||||
const volatile int pid_target = 0;
|
||||
|
||||
SEC("tracepoint/syscalls/sys_enter_openat")
|
||||
int tracepoint__syscalls__sys_enter_openat(struct trace_event_raw_sys_enter* ctx)
|
||||
{
|
||||
u64 id = bpf_get_current_pid_tgid();
|
||||
u32 pid = id;
|
||||
|
||||
if (pid_target && pid_target != pid)
|
||||
return false;
|
||||
// Use bpf_printk to print the process information
|
||||
bpf_printk("Process ID: %d enter sys openat\n", pid);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// "Trace open family syscalls."
|
||||
char LICENSE[] SEC("license") = "GPL";
|
||||
7
5-uprobe-bashreadline/.gitignore
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
.vscode
|
||||
package.json
|
||||
ecli
|
||||
*.o
|
||||
*.skel.json
|
||||
*.skel.yaml
|
||||
package.yaml
|
||||
38
5-uprobe-bashreadline/bashreadline.bpf.c
Normal file
@@ -0,0 +1,38 @@
|
||||
#include <vmlinux.h>
|
||||
#include <bpf/bpf_helpers.h>
|
||||
#include <bpf/bpf_tracing.h>
|
||||
|
||||
#define TASK_COMM_LEN 16
|
||||
#define MAX_LINE_SIZE 80
|
||||
|
||||
/* Format of u[ret]probe section definition supporting auto-attach:
|
||||
* u[ret]probe/binary:function[+offset]
|
||||
*
|
||||
* binary can be an absolute/relative path or a filename; the latter is resolved to a
|
||||
* full binary path via bpf_program__attach_uprobe_opts.
|
||||
*
|
||||
* Specifying uprobe+ ensures we carry out strict matching; either "uprobe" must be
|
||||
* specified (and auto-attach is not possible) or the above format is specified for
|
||||
* auto-attach.
|
||||
*/
|
||||
SEC("uretprobe//bin/bash:readline")
|
||||
int BPF_KRETPROBE(printret, const void *ret)
|
||||
{
|
||||
char str[MAX_LINE_SIZE];
|
||||
char comm[TASK_COMM_LEN];
|
||||
u32 pid;
|
||||
|
||||
if (!ret)
|
||||
return 0;
|
||||
|
||||
bpf_get_current_comm(&comm, sizeof(comm));
|
||||
|
||||
pid = bpf_get_current_pid_tgid() >> 32;
|
||||
bpf_probe_read_user_str(str, sizeof(str), ret);
|
||||
|
||||
bpf_printk("PID %d (%s) read: %s ", pid, comm, str);
|
||||
|
||||
return 0;
|
||||
};
|
||||
|
||||
char LICENSE[] SEC("license") = "GPL";
|
||||
276
5-uprobe-bashreadline/index.html
Normal file
10
6-sigsnoop/.gitignore
vendored
Executable file
@@ -0,0 +1,10 @@
|
||||
.vscode
|
||||
package.json
|
||||
*.wasm
|
||||
ewasm-skel.h
|
||||
ecli
|
||||
ewasm
|
||||
*.o
|
||||
*.skel.json
|
||||
*.skel.yaml
|
||||
package.yaml
|
||||
313
6-sigsnoop/index.html
Normal file
74
6-sigsnoop/sigsnoop.bpf.c
Executable file
@@ -0,0 +1,74 @@
|
||||
#include <vmlinux.h>
|
||||
#include <bpf/bpf_helpers.h>
|
||||
#include <bpf/bpf_tracing.h>
|
||||
|
||||
#define MAX_ENTRIES 10240
|
||||
#define TASK_COMM_LEN 16
|
||||
|
||||
struct event {
|
||||
unsigned int pid;
|
||||
unsigned int tpid;
|
||||
int sig;
|
||||
int ret;
|
||||
char comm[TASK_COMM_LEN];
|
||||
};
|
||||
|
||||
struct {
|
||||
__uint(type, BPF_MAP_TYPE_HASH);
|
||||
__uint(max_entries, MAX_ENTRIES);
|
||||
__type(key, __u32);
|
||||
__type(value, struct event);
|
||||
} values SEC(".maps");
|
||||
|
||||
|
||||
static int probe_entry(pid_t tpid, int sig)
|
||||
{
|
||||
struct event event = {};
|
||||
__u64 pid_tgid;
|
||||
__u32 tid;
|
||||
|
||||
pid_tgid = bpf_get_current_pid_tgid();
|
||||
tid = (__u32)pid_tgid;
|
||||
event.pid = pid_tgid >> 32;
|
||||
event.tpid = tpid;
|
||||
event.sig = sig;
|
||||
bpf_get_current_comm(event.comm, sizeof(event.comm));
|
||||
bpf_map_update_elem(&values, &tid, &event, BPF_ANY);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int probe_exit(void *ctx, int ret)
|
||||
{
|
||||
__u64 pid_tgid = bpf_get_current_pid_tgid();
|
||||
__u32 tid = (__u32)pid_tgid;
|
||||
struct event *eventp;
|
||||
|
||||
eventp = bpf_map_lookup_elem(&values, &tid);
|
||||
if (!eventp)
|
||||
return 0;
|
||||
|
||||
eventp->ret = ret;
|
||||
bpf_printk("PID %d (%s) sent signal %d to PID %d, ret = %d",
|
||||
eventp->pid, eventp->comm, eventp->sig, eventp->tpid, ret);
|
||||
|
||||
cleanup:
|
||||
bpf_map_delete_elem(&values, &tid);
|
||||
return 0;
|
||||
}
|
||||
|
||||
SEC("tracepoint/syscalls/sys_enter_kill")
|
||||
int kill_entry(struct trace_event_raw_sys_enter *ctx)
|
||||
{
|
||||
pid_t tpid = (pid_t)ctx->args[0];
|
||||
int sig = (int)ctx->args[1];
|
||||
|
||||
return probe_entry(tpid, sig);
|
||||
}
|
||||
|
||||
SEC("tracepoint/syscalls/sys_exit_kill")
|
||||
int kill_exit(struct trace_event_raw_sys_exit *ctx)
|
||||
{
|
||||
return probe_exit(ctx, ctx->ret);
|
||||
}
|
||||
|
||||
char LICENSE[] SEC("license") = "Dual BSD/GPL";
|
||||
3
7-execsnoop/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
ecli
|
||||
*.json
|
||||
|
||||
36
7-execsnoop/execsnoop.bpf.c
Normal file
@@ -0,0 +1,36 @@
|
||||
// SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause)
|
||||
#include <vmlinux.h>
|
||||
#include <bpf/bpf_helpers.h>
|
||||
#include <bpf/bpf_core_read.h>
|
||||
#include "execsnoop.h"
|
||||
|
||||
struct {
|
||||
__uint(type, BPF_MAP_TYPE_PERF_EVENT_ARRAY);
|
||||
__uint(key_size, sizeof(u32));
|
||||
__uint(value_size, sizeof(u32));
|
||||
} events SEC(".maps");
|
||||
|
||||
SEC("tracepoint/syscalls/sys_enter_execve")
|
||||
int tracepoint__syscalls__sys_enter_execve(struct trace_event_raw_sys_enter* ctx)
|
||||
{
|
||||
u64 id;
|
||||
pid_t pid, tgid;
|
||||
struct event event;
|
||||
struct task_struct *task;
|
||||
|
||||
uid_t uid = (u32)bpf_get_current_uid_gid();
|
||||
id = bpf_get_current_pid_tgid();
|
||||
pid = (pid_t)id;
|
||||
tgid = id >> 32;
|
||||
|
||||
event.pid = tgid;
|
||||
event.uid = uid;
|
||||
task = (struct task_struct*)bpf_get_current_task();
|
||||
event.ppid = BPF_CORE_READ(task, real_parent, tgid);
|
||||
bpf_get_current_comm(&event.comm, sizeof(event.comm));
|
||||
bpf_perf_event_output(ctx, &events, BPF_F_CURRENT_CPU, &event, sizeof(event));
|
||||
return 0;
|
||||
}
|
||||
|
||||
char LICENSE[] SEC("license") = "GPL";
|
||||
|
||||
16
7-execsnoop/execsnoop.h
Normal file
@@ -0,0 +1,16 @@
|
||||
/* SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) */
|
||||
#ifndef __EXECSNOOP_H
|
||||
#define __EXECSNOOP_H
|
||||
|
||||
#define TASK_COMM_LEN 16
|
||||
|
||||
struct event {
|
||||
int pid;
|
||||
int ppid;
|
||||
int uid;
|
||||
char comm[TASK_COMM_LEN];
|
||||
};
|
||||
|
||||
#endif /* __EXECSNOOP_H */
|
||||
|
||||
|
||||
291
7-execsnoop/index.html
Normal file
4
8-exitsnoop/.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
.vscode
|
||||
eunomia-exporter
|
||||
ecli
|
||||
*.json
|
||||
50
8-exitsnoop/exitsnoop.bpf.c
Normal file
@@ -0,0 +1,50 @@
|
||||
// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
|
||||
/* Copyright (c) 2020 Facebook */
|
||||
#include "vmlinux.h"
|
||||
#include <bpf/bpf_helpers.h>
|
||||
#include <bpf/bpf_tracing.h>
|
||||
#include <bpf/bpf_core_read.h>
|
||||
#include "exitsnoop.h"
|
||||
|
||||
char LICENSE[] SEC("license") = "Dual BSD/GPL";
|
||||
|
||||
struct {
|
||||
__uint(type, BPF_MAP_TYPE_RINGBUF);
|
||||
__uint(max_entries, 256 * 1024);
|
||||
} rb SEC(".maps");
|
||||
|
||||
SEC("tp/sched/sched_process_exit")
|
||||
int handle_exit(struct trace_event_raw_sched_process_template* ctx)
|
||||
{
|
||||
struct task_struct *task;
|
||||
struct event *e;
|
||||
pid_t pid, tid;
|
||||
u64 id, ts, *start_ts, duration_ns = 0;
|
||||
|
||||
/* get PID and TID of exiting thread/process */
|
||||
id = bpf_get_current_pid_tgid();
|
||||
pid = id >> 32;
|
||||
tid = (u32)id;
|
||||
|
||||
/* ignore thread exits */
|
||||
if (pid != tid)
|
||||
return 0;
|
||||
|
||||
/* reserve sample from BPF ringbuf */
|
||||
e = bpf_ringbuf_reserve(&rb, sizeof(*e), 0);
|
||||
if (!e)
|
||||
return 0;
|
||||
|
||||
/* fill out the sample with data */
|
||||
task = (struct task_struct *)bpf_get_current_task();
|
||||
|
||||
e->duration_ns = duration_ns;
|
||||
e->pid = pid;
|
||||
e->ppid = BPF_CORE_READ(task, real_parent, tgid);
|
||||
e->exit_code = (BPF_CORE_READ(task, exit_code) >> 8) & 0xff;
|
||||
bpf_get_current_comm(&e->comm, sizeof(e->comm));
|
||||
|
||||
/* send data to user-space for post-processing */
|
||||
bpf_ringbuf_submit(e, 0);
|
||||
return 0;
|
||||
}
|
||||
15
8-exitsnoop/exitsnoop.h
Normal file
@@ -0,0 +1,15 @@
|
||||
#ifndef __BOOTSTRAP_H
|
||||
#define __BOOTSTRAP_H
|
||||
|
||||
#define TASK_COMM_LEN 16
|
||||
#define MAX_FILENAME_LEN 127
|
||||
|
||||
struct event {
|
||||
int pid;
|
||||
int ppid;
|
||||
unsigned exit_code;
|
||||
unsigned long long duration_ns;
|
||||
char comm[TASK_COMM_LEN];
|
||||
};
|
||||
|
||||
#endif /* __BOOTSTRAP_H */
|
||||
318
8-exitsnoop/index.html
Normal file
7
9-runqlat/.gitignore
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
.vscode
|
||||
package.json
|
||||
*.o
|
||||
*.skel.json
|
||||
*.skel.yaml
|
||||
package.yaml
|
||||
ecli
|
||||
31
9-runqlat/bits.bpf.h
Normal file
@@ -0,0 +1,31 @@
|
||||
/* SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) */
|
||||
#ifndef __BITS_BPF_H
|
||||
#define __BITS_BPF_H
|
||||
|
||||
#define READ_ONCE(x) (*(volatile typeof(x) *)&(x))
|
||||
#define WRITE_ONCE(x, val) ((*(volatile typeof(x) *)&(x)) = val)
|
||||
|
||||
static __always_inline u64 log2(u32 v)
|
||||
{
|
||||
u32 shift, r;
|
||||
|
||||
r = (v > 0xFFFF) << 4; v >>= r;
|
||||
shift = (v > 0xFF) << 3; v >>= shift; r |= shift;
|
||||
shift = (v > 0xF) << 2; v >>= shift; r |= shift;
|
||||
shift = (v > 0x3) << 1; v >>= shift; r |= shift;
|
||||
r |= (v >> 1);
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
static __always_inline u64 log2l(u64 v)
|
||||
{
|
||||
u32 hi = v >> 32;
|
||||
|
||||
if (hi)
|
||||
return log2(hi) + 32;
|
||||
else
|
||||
return log2(v);
|
||||
}
|
||||
|
||||
#endif /* __BITS_BPF_H */
|
||||
112
9-runqlat/core_fixes.bpf.h
Normal file
@@ -0,0 +1,112 @@
|
||||
/* SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) */
|
||||
/* Copyright (c) 2021 Hengqi Chen */
|
||||
|
||||
#ifndef __CORE_FIXES_BPF_H
|
||||
#define __CORE_FIXES_BPF_H
|
||||
|
||||
#include <vmlinux.h>
|
||||
#include <bpf/bpf_core_read.h>
|
||||
|
||||
/**
|
||||
* commit 2f064a59a1 ("sched: Change task_struct::state") changes
|
||||
* the name of task_struct::state to task_struct::__state
|
||||
* see:
|
||||
* https://github.com/torvalds/linux/commit/2f064a59a1
|
||||
*/
|
||||
struct task_struct___o {
|
||||
volatile long int state;
|
||||
} __attribute__((preserve_access_index));
|
||||
|
||||
struct task_struct___x {
|
||||
unsigned int __state;
|
||||
} __attribute__((preserve_access_index));
|
||||
|
||||
static __always_inline __s64 get_task_state(void *task)
|
||||
{
|
||||
struct task_struct___x *t = task;
|
||||
|
||||
if (bpf_core_field_exists(t->__state))
|
||||
return BPF_CORE_READ(t, __state);
|
||||
return BPF_CORE_READ((struct task_struct___o *)task, state);
|
||||
}
|
||||
|
||||
/**
|
||||
* commit 309dca309fc3 ("block: store a block_device pointer in struct bio")
|
||||
* adds a new member bi_bdev which is a pointer to struct block_device
|
||||
* see:
|
||||
* https://github.com/torvalds/linux/commit/309dca309fc3
|
||||
*/
|
||||
struct bio___o {
|
||||
struct gendisk *bi_disk;
|
||||
} __attribute__((preserve_access_index));
|
||||
|
||||
struct bio___x {
|
||||
struct block_device *bi_bdev;
|
||||
} __attribute__((preserve_access_index));
|
||||
|
||||
static __always_inline struct gendisk *get_gendisk(void *bio)
|
||||
{
|
||||
struct bio___x *b = bio;
|
||||
|
||||
if (bpf_core_field_exists(b->bi_bdev))
|
||||
return BPF_CORE_READ(b, bi_bdev, bd_disk);
|
||||
return BPF_CORE_READ((struct bio___o *)bio, bi_disk);
|
||||
}
|
||||
|
||||
/**
|
||||
* commit d5869fdc189f ("block: introduce block_rq_error tracepoint")
|
||||
* adds a new tracepoint block_rq_error and it shares the same arguments
|
||||
* with tracepoint block_rq_complete. As a result, the kernel BTF now has
|
||||
* a `struct trace_event_raw_block_rq_completion` instead of
|
||||
* `struct trace_event_raw_block_rq_complete`.
|
||||
* see:
|
||||
* https://github.com/torvalds/linux/commit/d5869fdc189f
|
||||
*/
|
||||
struct trace_event_raw_block_rq_complete___x {
|
||||
dev_t dev;
|
||||
sector_t sector;
|
||||
unsigned int nr_sector;
|
||||
} __attribute__((preserve_access_index));
|
||||
|
||||
struct trace_event_raw_block_rq_completion___x {
|
||||
dev_t dev;
|
||||
sector_t sector;
|
||||
unsigned int nr_sector;
|
||||
} __attribute__((preserve_access_index));
|
||||
|
||||
static __always_inline bool has_block_rq_completion()
|
||||
{
|
||||
if (bpf_core_type_exists(struct trace_event_raw_block_rq_completion___x))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* commit d152c682f03c ("block: add an explicit ->disk backpointer to the
|
||||
* request_queue") and commit f3fa33acca9f ("block: remove the ->rq_disk
|
||||
* field in struct request") make some changes to `struct request` and
|
||||
* `struct request_queue`. Now, to get the `struct gendisk *` field in a CO-RE
|
||||
* way, we need both `struct request` and `struct request_queue`.
|
||||
* see:
|
||||
* https://github.com/torvalds/linux/commit/d152c682f03c
|
||||
* https://github.com/torvalds/linux/commit/f3fa33acca9f
|
||||
*/
|
||||
struct request_queue___x {
|
||||
struct gendisk *disk;
|
||||
} __attribute__((preserve_access_index));
|
||||
|
||||
struct request___x {
|
||||
struct request_queue___x *q;
|
||||
struct gendisk *rq_disk;
|
||||
} __attribute__((preserve_access_index));
|
||||
|
||||
static __always_inline struct gendisk *get_disk(void *request)
|
||||
{
|
||||
struct request___x *r = request;
|
||||
|
||||
if (bpf_core_field_exists(r->rq_disk))
|
||||
return BPF_CORE_READ(r, rq_disk);
|
||||
return BPF_CORE_READ(r, q, disk);
|
||||
}
|
||||
|
||||
#endif /* __CORE_FIXES_BPF_H */
|
||||
446
9-runqlat/index.html
Normal file
26
9-runqlat/maps.bpf.h
Normal file
@@ -0,0 +1,26 @@
|
||||
// SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause)
|
||||
// Copyright (c) 2020 Anton Protopopov
|
||||
#ifndef __MAPS_BPF_H
|
||||
#define __MAPS_BPF_H
|
||||
|
||||
#include <bpf/bpf_helpers.h>
|
||||
#include <asm-generic/errno.h>
|
||||
|
||||
static __always_inline void *
|
||||
bpf_map_lookup_or_try_init(void *map, const void *key, const void *init)
|
||||
{
|
||||
void *val;
|
||||
long err;
|
||||
|
||||
val = bpf_map_lookup_elem(map, key);
|
||||
if (val)
|
||||
return val;
|
||||
|
||||
err = bpf_map_update_elem(map, key, init, BPF_NOEXIST);
|
||||
if (err && err != -EEXIST)
|
||||
return 0;
|
||||
|
||||
return bpf_map_lookup_elem(map, key);
|
||||
}
|
||||
|
||||
#endif /* __MAPS_BPF_H */
|
||||
152
9-runqlat/runqlat.bpf.c
Normal file
@@ -0,0 +1,152 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
// Copyright (c) 2020 Wenbo Zhang
|
||||
#include <vmlinux.h>
|
||||
#include <bpf/bpf_helpers.h>
|
||||
#include <bpf/bpf_core_read.h>
|
||||
#include <bpf/bpf_tracing.h>
|
||||
#include "runqlat.h"
|
||||
#include "bits.bpf.h"
|
||||
#include "maps.bpf.h"
|
||||
#include "core_fixes.bpf.h"
|
||||
|
||||
#define MAX_ENTRIES 10240
|
||||
#define TASK_RUNNING 0
|
||||
|
||||
const volatile bool filter_cg = false;
|
||||
const volatile bool targ_per_process = false;
|
||||
const volatile bool targ_per_thread = false;
|
||||
const volatile bool targ_per_pidns = false;
|
||||
const volatile bool targ_ms = false;
|
||||
const volatile pid_t targ_tgid = 0;
|
||||
|
||||
struct {
|
||||
__uint(type, BPF_MAP_TYPE_CGROUP_ARRAY);
|
||||
__type(key, u32);
|
||||
__type(value, u32);
|
||||
__uint(max_entries, 1);
|
||||
} cgroup_map SEC(".maps");
|
||||
|
||||
struct {
|
||||
__uint(type, BPF_MAP_TYPE_HASH);
|
||||
__uint(max_entries, MAX_ENTRIES);
|
||||
__type(key, u32);
|
||||
__type(value, u64);
|
||||
} start SEC(".maps");
|
||||
|
||||
static struct hist zero;
|
||||
|
||||
/// @sample {"interval": 1000, "type" : "log2_hist"}
|
||||
struct {
|
||||
__uint(type, BPF_MAP_TYPE_HASH);
|
||||
__uint(max_entries, MAX_ENTRIES);
|
||||
__type(key, u32);
|
||||
__type(value, struct hist);
|
||||
} hists SEC(".maps");
|
||||
|
||||
static int trace_enqueue(u32 tgid, u32 pid)
|
||||
{
|
||||
u64 ts;
|
||||
|
||||
if (!pid)
|
||||
return 0;
|
||||
if (targ_tgid && targ_tgid != tgid)
|
||||
return 0;
|
||||
|
||||
ts = bpf_ktime_get_ns();
|
||||
bpf_map_update_elem(&start, &pid, &ts, BPF_ANY);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static unsigned int pid_namespace(struct task_struct *task)
|
||||
{
|
||||
struct pid *pid;
|
||||
unsigned int level;
|
||||
struct upid upid;
|
||||
unsigned int inum;
|
||||
|
||||
/* get the pid namespace by following task_active_pid_ns(),
|
||||
* pid->numbers[pid->level].ns
|
||||
*/
|
||||
pid = BPF_CORE_READ(task, thread_pid);
|
||||
level = BPF_CORE_READ(pid, level);
|
||||
bpf_core_read(&upid, sizeof(upid), &pid->numbers[level]);
|
||||
inum = BPF_CORE_READ(upid.ns, ns.inum);
|
||||
|
||||
return inum;
|
||||
}
|
||||
|
||||
static int handle_switch(bool preempt, struct task_struct *prev, struct task_struct *next)
|
||||
{
|
||||
struct hist *histp;
|
||||
u64 *tsp, slot;
|
||||
u32 pid, hkey;
|
||||
s64 delta;
|
||||
|
||||
if (filter_cg && !bpf_current_task_under_cgroup(&cgroup_map, 0))
|
||||
return 0;
|
||||
|
||||
if (get_task_state(prev) == TASK_RUNNING)
|
||||
trace_enqueue(BPF_CORE_READ(prev, tgid), BPF_CORE_READ(prev, pid));
|
||||
|
||||
pid = BPF_CORE_READ(next, pid);
|
||||
|
||||
tsp = bpf_map_lookup_elem(&start, &pid);
|
||||
if (!tsp)
|
||||
return 0;
|
||||
delta = bpf_ktime_get_ns() - *tsp;
|
||||
if (delta < 0)
|
||||
goto cleanup;
|
||||
|
||||
if (targ_per_process)
|
||||
hkey = BPF_CORE_READ(next, tgid);
|
||||
else if (targ_per_thread)
|
||||
hkey = pid;
|
||||
else if (targ_per_pidns)
|
||||
hkey = pid_namespace(next);
|
||||
else
|
||||
hkey = -1;
|
||||
histp = bpf_map_lookup_or_try_init(&hists, &hkey, &zero);
|
||||
if (!histp)
|
||||
goto cleanup;
|
||||
if (!histp->comm[0])
|
||||
bpf_probe_read_kernel_str(&histp->comm, sizeof(histp->comm),
|
||||
next->comm);
|
||||
if (targ_ms)
|
||||
delta /= 1000000U;
|
||||
else
|
||||
delta /= 1000U;
|
||||
slot = log2l(delta);
|
||||
if (slot >= MAX_SLOTS)
|
||||
slot = MAX_SLOTS - 1;
|
||||
__sync_fetch_and_add(&histp->slots[slot], 1);
|
||||
|
||||
cleanup:
|
||||
bpf_map_delete_elem(&start, &pid);
|
||||
return 0;
|
||||
}
|
||||
|
||||
SEC("raw_tp/sched_wakeup")
|
||||
int BPF_PROG(handle_sched_wakeup, struct task_struct *p)
|
||||
{
|
||||
if (filter_cg && !bpf_current_task_under_cgroup(&cgroup_map, 0))
|
||||
return 0;
|
||||
|
||||
return trace_enqueue(BPF_CORE_READ(p, tgid), BPF_CORE_READ(p, pid));
|
||||
}
|
||||
|
||||
SEC("raw_tp/sched_wakeup_new")
|
||||
int BPF_PROG(handle_sched_wakeup_new, struct task_struct *p)
|
||||
{
|
||||
if (filter_cg && !bpf_current_task_under_cgroup(&cgroup_map, 0))
|
||||
return 0;
|
||||
|
||||
return trace_enqueue(BPF_CORE_READ(p, tgid), BPF_CORE_READ(p, pid));
|
||||
}
|
||||
|
||||
SEC("raw_tp/sched_switch")
|
||||
int BPF_PROG(handle_sched_switch, bool preempt, struct task_struct *prev, struct task_struct *next)
|
||||
{
|
||||
return handle_switch(preempt, prev, next);
|
||||
}
|
||||
|
||||
char LICENSE[] SEC("license") = "GPL";
|
||||
14
9-runqlat/runqlat.h
Normal file
@@ -0,0 +1,14 @@
|
||||
|
||||
/* SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) */
|
||||
#ifndef __RUNQLAT_H
|
||||
#define __RUNQLAT_H
|
||||
|
||||
#define TASK_COMM_LEN 16
|
||||
#define MAX_SLOTS 26
|
||||
|
||||
struct hist {
|
||||
__u32 slots[MAX_SLOTS];
|
||||
char comm[TASK_COMM_LEN];
|
||||
};
|
||||
|
||||
#endif /* __RUNQLAT_H */
|
||||
4
FontAwesome/css/font-awesome.css
vendored
Normal file
BIN
FontAwesome/fonts/FontAwesome.ttf
Normal file
BIN
FontAwesome/fonts/fontawesome-webfont.eot
Normal file
2671
FontAwesome/fonts/fontawesome-webfont.svg
Normal file
|
After Width: | Height: | Size: 434 KiB |
BIN
FontAwesome/fonts/fontawesome-webfont.ttf
Normal file
BIN
FontAwesome/fonts/fontawesome-webfont.woff
Normal file
BIN
FontAwesome/fonts/fontawesome-webfont.woff2
Normal file
78
ayu-highlight.css
Normal file
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
Based off of the Ayu theme
|
||||
Original by Dempfi (https://github.com/dempfi/ayu)
|
||||
*/
|
||||
|
||||
.hljs {
|
||||
display: block;
|
||||
overflow-x: auto;
|
||||
background: #191f26;
|
||||
color: #e6e1cf;
|
||||
}
|
||||
|
||||
.hljs-comment,
|
||||
.hljs-quote {
|
||||
color: #5c6773;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.hljs-variable,
|
||||
.hljs-template-variable,
|
||||
.hljs-attribute,
|
||||
.hljs-attr,
|
||||
.hljs-regexp,
|
||||
.hljs-link,
|
||||
.hljs-selector-id,
|
||||
.hljs-selector-class {
|
||||
color: #ff7733;
|
||||
}
|
||||
|
||||
.hljs-number,
|
||||
.hljs-meta,
|
||||
.hljs-builtin-name,
|
||||
.hljs-literal,
|
||||
.hljs-type,
|
||||
.hljs-params {
|
||||
color: #ffee99;
|
||||
}
|
||||
|
||||
.hljs-string,
|
||||
.hljs-bullet {
|
||||
color: #b8cc52;
|
||||
}
|
||||
|
||||
.hljs-title,
|
||||
.hljs-built_in,
|
||||
.hljs-section {
|
||||
color: #ffb454;
|
||||
}
|
||||
|
||||
.hljs-keyword,
|
||||
.hljs-selector-tag,
|
||||
.hljs-symbol {
|
||||
color: #ff7733;
|
||||
}
|
||||
|
||||
.hljs-name {
|
||||
color: #36a3d9;
|
||||
}
|
||||
|
||||
.hljs-tag {
|
||||
color: #00568d;
|
||||
}
|
||||
|
||||
.hljs-emphasis {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.hljs-strong {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.hljs-addition {
|
||||
color: #91b362;
|
||||
}
|
||||
|
||||
.hljs-deletion {
|
||||
color: #d96c75;
|
||||
}
|
||||
685
bcc-documents/kernel-versions.html
Normal file
243
bcc-documents/kernel_config.html
Normal file
2000
bcc-documents/reference_guide.html
Normal file
279
bcc-documents/special_filtering.html
Normal file
512
bcc-documents/tutorial.html
Normal file
826
bcc-documents/tutorial_bcc_python_developer.html
Normal file
688
book.js
Normal file
@@ -0,0 +1,688 @@
|
||||
"use strict";
|
||||
|
||||
// Fix back button cache problem
|
||||
window.onunload = function () { };
|
||||
|
||||
// Global variable, shared between modules
|
||||
function playground_text(playground, hidden = true) {
|
||||
let code_block = playground.querySelector("code");
|
||||
|
||||
if (window.ace && code_block.classList.contains("editable")) {
|
||||
let editor = window.ace.edit(code_block);
|
||||
return editor.getValue();
|
||||
} else if (hidden) {
|
||||
return code_block.textContent;
|
||||
} else {
|
||||
return code_block.innerText;
|
||||
}
|
||||
}
|
||||
|
||||
(function codeSnippets() {
|
||||
function fetch_with_timeout(url, options, timeout = 6000) {
|
||||
return Promise.race([
|
||||
fetch(url, options),
|
||||
new Promise((_, reject) => setTimeout(() => reject(new Error('timeout')), timeout))
|
||||
]);
|
||||
}
|
||||
|
||||
var playgrounds = Array.from(document.querySelectorAll(".playground"));
|
||||
if (playgrounds.length > 0) {
|
||||
fetch_with_timeout("https://play.rust-lang.org/meta/crates", {
|
||||
headers: {
|
||||
'Content-Type': "application/json",
|
||||
},
|
||||
method: 'POST',
|
||||
mode: 'cors',
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(response => {
|
||||
// get list of crates available in the rust playground
|
||||
let playground_crates = response.crates.map(item => item["id"]);
|
||||
playgrounds.forEach(block => handle_crate_list_update(block, playground_crates));
|
||||
});
|
||||
}
|
||||
|
||||
function handle_crate_list_update(playground_block, playground_crates) {
|
||||
// update the play buttons after receiving the response
|
||||
update_play_button(playground_block, playground_crates);
|
||||
|
||||
// and install on change listener to dynamically update ACE editors
|
||||
if (window.ace) {
|
||||
let code_block = playground_block.querySelector("code");
|
||||
if (code_block.classList.contains("editable")) {
|
||||
let editor = window.ace.edit(code_block);
|
||||
editor.addEventListener("change", function (e) {
|
||||
update_play_button(playground_block, playground_crates);
|
||||
});
|
||||
// add Ctrl-Enter command to execute rust code
|
||||
editor.commands.addCommand({
|
||||
name: "run",
|
||||
bindKey: {
|
||||
win: "Ctrl-Enter",
|
||||
mac: "Ctrl-Enter"
|
||||
},
|
||||
exec: _editor => run_rust_code(playground_block)
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// updates the visibility of play button based on `no_run` class and
|
||||
// used crates vs ones available on http://play.rust-lang.org
|
||||
function update_play_button(pre_block, playground_crates) {
|
||||
var play_button = pre_block.querySelector(".play-button");
|
||||
|
||||
// skip if code is `no_run`
|
||||
if (pre_block.querySelector('code').classList.contains("no_run")) {
|
||||
play_button.classList.add("hidden");
|
||||
return;
|
||||
}
|
||||
|
||||
// get list of `extern crate`'s from snippet
|
||||
var txt = playground_text(pre_block);
|
||||
var re = /extern\s+crate\s+([a-zA-Z_0-9]+)\s*;/g;
|
||||
var snippet_crates = [];
|
||||
var item;
|
||||
while (item = re.exec(txt)) {
|
||||
snippet_crates.push(item[1]);
|
||||
}
|
||||
|
||||
// check if all used crates are available on play.rust-lang.org
|
||||
var all_available = snippet_crates.every(function (elem) {
|
||||
return playground_crates.indexOf(elem) > -1;
|
||||
});
|
||||
|
||||
if (all_available) {
|
||||
play_button.classList.remove("hidden");
|
||||
} else {
|
||||
play_button.classList.add("hidden");
|
||||
}
|
||||
}
|
||||
|
||||
function run_rust_code(code_block) {
|
||||
var result_block = code_block.querySelector(".result");
|
||||
if (!result_block) {
|
||||
result_block = document.createElement('code');
|
||||
result_block.className = 'result hljs language-bash';
|
||||
|
||||
code_block.append(result_block);
|
||||
}
|
||||
|
||||
let text = playground_text(code_block);
|
||||
let classes = code_block.querySelector('code').classList;
|
||||
let edition = "2015";
|
||||
if(classes.contains("edition2018")) {
|
||||
edition = "2018";
|
||||
} else if(classes.contains("edition2021")) {
|
||||
edition = "2021";
|
||||
}
|
||||
var params = {
|
||||
version: "stable",
|
||||
optimize: "0",
|
||||
code: text,
|
||||
edition: edition
|
||||
};
|
||||
|
||||
if (text.indexOf("#![feature") !== -1) {
|
||||
params.version = "nightly";
|
||||
}
|
||||
|
||||
result_block.innerText = "Running...";
|
||||
|
||||
fetch_with_timeout("https://play.rust-lang.org/evaluate.json", {
|
||||
headers: {
|
||||
'Content-Type': "application/json",
|
||||
},
|
||||
method: 'POST',
|
||||
mode: 'cors',
|
||||
body: JSON.stringify(params)
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(response => {
|
||||
if (response.result.trim() === '') {
|
||||
result_block.innerText = "No output";
|
||||
result_block.classList.add("result-no-output");
|
||||
} else {
|
||||
result_block.innerText = response.result;
|
||||
result_block.classList.remove("result-no-output");
|
||||
}
|
||||
})
|
||||
.catch(error => result_block.innerText = "Playground Communication: " + error.message);
|
||||
}
|
||||
|
||||
// Syntax highlighting Configuration
|
||||
hljs.configure({
|
||||
tabReplace: ' ', // 4 spaces
|
||||
languages: [], // Languages used for auto-detection
|
||||
});
|
||||
|
||||
let code_nodes = Array
|
||||
.from(document.querySelectorAll('code'))
|
||||
// Don't highlight `inline code` blocks in headers.
|
||||
.filter(function (node) {return !node.parentElement.classList.contains("header"); });
|
||||
|
||||
if (window.ace) {
|
||||
// language-rust class needs to be removed for editable
|
||||
// blocks or highlightjs will capture events
|
||||
code_nodes
|
||||
.filter(function (node) {return node.classList.contains("editable"); })
|
||||
.forEach(function (block) { block.classList.remove('language-rust'); });
|
||||
|
||||
code_nodes
|
||||
.filter(function (node) {return !node.classList.contains("editable"); })
|
||||
.forEach(function (block) { hljs.highlightBlock(block); });
|
||||
} else {
|
||||
code_nodes.forEach(function (block) { hljs.highlightBlock(block); });
|
||||
}
|
||||
|
||||
// Adding the hljs class gives code blocks the color css
|
||||
// even if highlighting doesn't apply
|
||||
code_nodes.forEach(function (block) { block.classList.add('hljs'); });
|
||||
|
||||
Array.from(document.querySelectorAll("code.language-rust")).forEach(function (block) {
|
||||
|
||||
var lines = Array.from(block.querySelectorAll('.boring'));
|
||||
// If no lines were hidden, return
|
||||
if (!lines.length) { return; }
|
||||
block.classList.add("hide-boring");
|
||||
|
||||
var buttons = document.createElement('div');
|
||||
buttons.className = 'buttons';
|
||||
buttons.innerHTML = "<button class=\"fa fa-eye\" title=\"Show hidden lines\" aria-label=\"Show hidden lines\"></button>";
|
||||
|
||||
// add expand button
|
||||
var pre_block = block.parentNode;
|
||||
pre_block.insertBefore(buttons, pre_block.firstChild);
|
||||
|
||||
pre_block.querySelector('.buttons').addEventListener('click', function (e) {
|
||||
if (e.target.classList.contains('fa-eye')) {
|
||||
e.target.classList.remove('fa-eye');
|
||||
e.target.classList.add('fa-eye-slash');
|
||||
e.target.title = 'Hide lines';
|
||||
e.target.setAttribute('aria-label', e.target.title);
|
||||
|
||||
block.classList.remove('hide-boring');
|
||||
} else if (e.target.classList.contains('fa-eye-slash')) {
|
||||
e.target.classList.remove('fa-eye-slash');
|
||||
e.target.classList.add('fa-eye');
|
||||
e.target.title = 'Show hidden lines';
|
||||
e.target.setAttribute('aria-label', e.target.title);
|
||||
|
||||
block.classList.add('hide-boring');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
if (window.playground_copyable) {
|
||||
Array.from(document.querySelectorAll('pre code')).forEach(function (block) {
|
||||
var pre_block = block.parentNode;
|
||||
if (!pre_block.classList.contains('playground')) {
|
||||
var buttons = pre_block.querySelector(".buttons");
|
||||
if (!buttons) {
|
||||
buttons = document.createElement('div');
|
||||
buttons.className = 'buttons';
|
||||
pre_block.insertBefore(buttons, pre_block.firstChild);
|
||||
}
|
||||
|
||||
var clipButton = document.createElement('button');
|
||||
clipButton.className = 'fa fa-copy clip-button';
|
||||
clipButton.title = 'Copy to clipboard';
|
||||
clipButton.setAttribute('aria-label', clipButton.title);
|
||||
clipButton.innerHTML = '<i class=\"tooltiptext\"></i>';
|
||||
|
||||
buttons.insertBefore(clipButton, buttons.firstChild);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Process playground code blocks
|
||||
Array.from(document.querySelectorAll(".playground")).forEach(function (pre_block) {
|
||||
// Add play button
|
||||
var buttons = pre_block.querySelector(".buttons");
|
||||
if (!buttons) {
|
||||
buttons = document.createElement('div');
|
||||
buttons.className = 'buttons';
|
||||
pre_block.insertBefore(buttons, pre_block.firstChild);
|
||||
}
|
||||
|
||||
var runCodeButton = document.createElement('button');
|
||||
runCodeButton.className = 'fa fa-play play-button';
|
||||
runCodeButton.hidden = true;
|
||||
runCodeButton.title = 'Run this code';
|
||||
runCodeButton.setAttribute('aria-label', runCodeButton.title);
|
||||
|
||||
buttons.insertBefore(runCodeButton, buttons.firstChild);
|
||||
runCodeButton.addEventListener('click', function (e) {
|
||||
run_rust_code(pre_block);
|
||||
});
|
||||
|
||||
if (window.playground_copyable) {
|
||||
var copyCodeClipboardButton = document.createElement('button');
|
||||
copyCodeClipboardButton.className = 'fa fa-copy clip-button';
|
||||
copyCodeClipboardButton.innerHTML = '<i class="tooltiptext"></i>';
|
||||
copyCodeClipboardButton.title = 'Copy to clipboard';
|
||||
copyCodeClipboardButton.setAttribute('aria-label', copyCodeClipboardButton.title);
|
||||
|
||||
buttons.insertBefore(copyCodeClipboardButton, buttons.firstChild);
|
||||
}
|
||||
|
||||
let code_block = pre_block.querySelector("code");
|
||||
if (window.ace && code_block.classList.contains("editable")) {
|
||||
var undoChangesButton = document.createElement('button');
|
||||
undoChangesButton.className = 'fa fa-history reset-button';
|
||||
undoChangesButton.title = 'Undo changes';
|
||||
undoChangesButton.setAttribute('aria-label', undoChangesButton.title);
|
||||
|
||||
buttons.insertBefore(undoChangesButton, buttons.firstChild);
|
||||
|
||||
undoChangesButton.addEventListener('click', function () {
|
||||
let editor = window.ace.edit(code_block);
|
||||
editor.setValue(editor.originalCode);
|
||||
editor.clearSelection();
|
||||
});
|
||||
}
|
||||
});
|
||||
})();
|
||||
|
||||
(function themes() {
|
||||
var html = document.querySelector('html');
|
||||
var themeToggleButton = document.getElementById('theme-toggle');
|
||||
var themePopup = document.getElementById('theme-list');
|
||||
var themeColorMetaTag = document.querySelector('meta[name="theme-color"]');
|
||||
var stylesheets = {
|
||||
ayuHighlight: document.querySelector("[href$='ayu-highlight.css']"),
|
||||
tomorrowNight: document.querySelector("[href$='tomorrow-night.css']"),
|
||||
highlight: document.querySelector("[href$='highlight.css']"),
|
||||
};
|
||||
|
||||
function showThemes() {
|
||||
themePopup.style.display = 'block';
|
||||
themeToggleButton.setAttribute('aria-expanded', true);
|
||||
themePopup.querySelector("button#" + get_theme()).focus();
|
||||
}
|
||||
|
||||
function updateThemeSelected() {
|
||||
themePopup.querySelectorAll('.theme-selected').forEach(function (el) {
|
||||
el.classList.remove('theme-selected');
|
||||
});
|
||||
themePopup.querySelector("button#" + get_theme()).classList.add('theme-selected');
|
||||
}
|
||||
|
||||
function hideThemes() {
|
||||
themePopup.style.display = 'none';
|
||||
themeToggleButton.setAttribute('aria-expanded', false);
|
||||
themeToggleButton.focus();
|
||||
}
|
||||
|
||||
function get_theme() {
|
||||
var theme;
|
||||
try { theme = localStorage.getItem('mdbook-theme'); } catch (e) { }
|
||||
if (theme === null || theme === undefined) {
|
||||
return default_theme;
|
||||
} else {
|
||||
return theme;
|
||||
}
|
||||
}
|
||||
|
||||
function set_theme(theme, store = true) {
|
||||
let ace_theme;
|
||||
|
||||
if (theme == 'coal' || theme == 'navy') {
|
||||
stylesheets.ayuHighlight.disabled = true;
|
||||
stylesheets.tomorrowNight.disabled = false;
|
||||
stylesheets.highlight.disabled = true;
|
||||
|
||||
ace_theme = "ace/theme/tomorrow_night";
|
||||
} else if (theme == 'ayu') {
|
||||
stylesheets.ayuHighlight.disabled = false;
|
||||
stylesheets.tomorrowNight.disabled = true;
|
||||
stylesheets.highlight.disabled = true;
|
||||
ace_theme = "ace/theme/tomorrow_night";
|
||||
} else {
|
||||
stylesheets.ayuHighlight.disabled = true;
|
||||
stylesheets.tomorrowNight.disabled = true;
|
||||
stylesheets.highlight.disabled = false;
|
||||
ace_theme = "ace/theme/dawn";
|
||||
}
|
||||
|
||||
setTimeout(function () {
|
||||
themeColorMetaTag.content = getComputedStyle(document.body).backgroundColor;
|
||||
}, 1);
|
||||
|
||||
if (window.ace && window.editors) {
|
||||
window.editors.forEach(function (editor) {
|
||||
editor.setTheme(ace_theme);
|
||||
});
|
||||
}
|
||||
|
||||
var previousTheme = get_theme();
|
||||
|
||||
if (store) {
|
||||
try { localStorage.setItem('mdbook-theme', theme); } catch (e) { }
|
||||
}
|
||||
|
||||
html.classList.remove(previousTheme);
|
||||
html.classList.add(theme);
|
||||
updateThemeSelected();
|
||||
}
|
||||
|
||||
// Set theme
|
||||
var theme = get_theme();
|
||||
|
||||
set_theme(theme, false);
|
||||
|
||||
themeToggleButton.addEventListener('click', function () {
|
||||
if (themePopup.style.display === 'block') {
|
||||
hideThemes();
|
||||
} else {
|
||||
showThemes();
|
||||
}
|
||||
});
|
||||
|
||||
themePopup.addEventListener('click', function (e) {
|
||||
var theme;
|
||||
if (e.target.className === "theme") {
|
||||
theme = e.target.id;
|
||||
} else if (e.target.parentElement.className === "theme") {
|
||||
theme = e.target.parentElement.id;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
set_theme(theme);
|
||||
});
|
||||
|
||||
themePopup.addEventListener('focusout', function(e) {
|
||||
// e.relatedTarget is null in Safari and Firefox on macOS (see workaround below)
|
||||
if (!!e.relatedTarget && !themeToggleButton.contains(e.relatedTarget) && !themePopup.contains(e.relatedTarget)) {
|
||||
hideThemes();
|
||||
}
|
||||
});
|
||||
|
||||
// Should not be needed, but it works around an issue on macOS & iOS: https://github.com/rust-lang/mdBook/issues/628
|
||||
document.addEventListener('click', function(e) {
|
||||
if (themePopup.style.display === 'block' && !themeToggleButton.contains(e.target) && !themePopup.contains(e.target)) {
|
||||
hideThemes();
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener('keydown', function (e) {
|
||||
if (e.altKey || e.ctrlKey || e.metaKey || e.shiftKey) { return; }
|
||||
if (!themePopup.contains(e.target)) { return; }
|
||||
|
||||
switch (e.key) {
|
||||
case 'Escape':
|
||||
e.preventDefault();
|
||||
hideThemes();
|
||||
break;
|
||||
case 'ArrowUp':
|
||||
e.preventDefault();
|
||||
var li = document.activeElement.parentElement;
|
||||
if (li && li.previousElementSibling) {
|
||||
li.previousElementSibling.querySelector('button').focus();
|
||||
}
|
||||
break;
|
||||
case 'ArrowDown':
|
||||
e.preventDefault();
|
||||
var li = document.activeElement.parentElement;
|
||||
if (li && li.nextElementSibling) {
|
||||
li.nextElementSibling.querySelector('button').focus();
|
||||
}
|
||||
break;
|
||||
case 'Home':
|
||||
e.preventDefault();
|
||||
themePopup.querySelector('li:first-child button').focus();
|
||||
break;
|
||||
case 'End':
|
||||
e.preventDefault();
|
||||
themePopup.querySelector('li:last-child button').focus();
|
||||
break;
|
||||
}
|
||||
});
|
||||
})();
|
||||
|
||||
(function sidebar() {
|
||||
var html = document.querySelector("html");
|
||||
var sidebar = document.getElementById("sidebar");
|
||||
var sidebarLinks = document.querySelectorAll('#sidebar a');
|
||||
var sidebarToggleButton = document.getElementById("sidebar-toggle");
|
||||
var sidebarResizeHandle = document.getElementById("sidebar-resize-handle");
|
||||
var firstContact = null;
|
||||
|
||||
function showSidebar() {
|
||||
html.classList.remove('sidebar-hidden')
|
||||
html.classList.add('sidebar-visible');
|
||||
Array.from(sidebarLinks).forEach(function (link) {
|
||||
link.setAttribute('tabIndex', 0);
|
||||
});
|
||||
sidebarToggleButton.setAttribute('aria-expanded', true);
|
||||
sidebar.setAttribute('aria-hidden', false);
|
||||
try { localStorage.setItem('mdbook-sidebar', 'visible'); } catch (e) { }
|
||||
}
|
||||
|
||||
|
||||
var sidebarAnchorToggles = document.querySelectorAll('#sidebar a.toggle');
|
||||
|
||||
function toggleSection(ev) {
|
||||
ev.currentTarget.parentElement.classList.toggle('expanded');
|
||||
}
|
||||
|
||||
Array.from(sidebarAnchorToggles).forEach(function (el) {
|
||||
el.addEventListener('click', toggleSection);
|
||||
});
|
||||
|
||||
function hideSidebar() {
|
||||
html.classList.remove('sidebar-visible')
|
||||
html.classList.add('sidebar-hidden');
|
||||
Array.from(sidebarLinks).forEach(function (link) {
|
||||
link.setAttribute('tabIndex', -1);
|
||||
});
|
||||
sidebarToggleButton.setAttribute('aria-expanded', false);
|
||||
sidebar.setAttribute('aria-hidden', true);
|
||||
try { localStorage.setItem('mdbook-sidebar', 'hidden'); } catch (e) { }
|
||||
}
|
||||
|
||||
// Toggle sidebar
|
||||
sidebarToggleButton.addEventListener('click', function sidebarToggle() {
|
||||
if (html.classList.contains("sidebar-hidden")) {
|
||||
var current_width = parseInt(
|
||||
document.documentElement.style.getPropertyValue('--sidebar-width'), 10);
|
||||
if (current_width < 150) {
|
||||
document.documentElement.style.setProperty('--sidebar-width', '150px');
|
||||
}
|
||||
showSidebar();
|
||||
} else if (html.classList.contains("sidebar-visible")) {
|
||||
hideSidebar();
|
||||
} else {
|
||||
if (getComputedStyle(sidebar)['transform'] === 'none') {
|
||||
hideSidebar();
|
||||
} else {
|
||||
showSidebar();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
sidebarResizeHandle.addEventListener('mousedown', initResize, false);
|
||||
|
||||
function initResize(e) {
|
||||
window.addEventListener('mousemove', resize, false);
|
||||
window.addEventListener('mouseup', stopResize, false);
|
||||
html.classList.add('sidebar-resizing');
|
||||
}
|
||||
function resize(e) {
|
||||
var pos = (e.clientX - sidebar.offsetLeft);
|
||||
if (pos < 20) {
|
||||
hideSidebar();
|
||||
} else {
|
||||
if (html.classList.contains("sidebar-hidden")) {
|
||||
showSidebar();
|
||||
}
|
||||
pos = Math.min(pos, window.innerWidth - 100);
|
||||
document.documentElement.style.setProperty('--sidebar-width', pos + 'px');
|
||||
}
|
||||
}
|
||||
//on mouseup remove windows functions mousemove & mouseup
|
||||
function stopResize(e) {
|
||||
html.classList.remove('sidebar-resizing');
|
||||
window.removeEventListener('mousemove', resize, false);
|
||||
window.removeEventListener('mouseup', stopResize, false);
|
||||
}
|
||||
|
||||
document.addEventListener('touchstart', function (e) {
|
||||
firstContact = {
|
||||
x: e.touches[0].clientX,
|
||||
time: Date.now()
|
||||
};
|
||||
}, { passive: true });
|
||||
|
||||
document.addEventListener('touchmove', function (e) {
|
||||
if (!firstContact)
|
||||
return;
|
||||
|
||||
var curX = e.touches[0].clientX;
|
||||
var xDiff = curX - firstContact.x,
|
||||
tDiff = Date.now() - firstContact.time;
|
||||
|
||||
if (tDiff < 250 && Math.abs(xDiff) >= 150) {
|
||||
if (xDiff >= 0 && firstContact.x < Math.min(document.body.clientWidth * 0.25, 300))
|
||||
showSidebar();
|
||||
else if (xDiff < 0 && curX < 300)
|
||||
hideSidebar();
|
||||
|
||||
firstContact = null;
|
||||
}
|
||||
}, { passive: true });
|
||||
|
||||
// Scroll sidebar to current active section
|
||||
var activeSection = document.getElementById("sidebar").querySelector(".active");
|
||||
if (activeSection) {
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoView
|
||||
activeSection.scrollIntoView({ block: 'center' });
|
||||
}
|
||||
})();
|
||||
|
||||
(function chapterNavigation() {
|
||||
document.addEventListener('keydown', function (e) {
|
||||
if (e.altKey || e.ctrlKey || e.metaKey || e.shiftKey) { return; }
|
||||
if (window.search && window.search.hasFocus()) { return; }
|
||||
|
||||
switch (e.key) {
|
||||
case 'ArrowRight':
|
||||
e.preventDefault();
|
||||
var nextButton = document.querySelector('.nav-chapters.next');
|
||||
if (nextButton) {
|
||||
window.location.href = nextButton.href;
|
||||
}
|
||||
break;
|
||||
case 'ArrowLeft':
|
||||
e.preventDefault();
|
||||
var previousButton = document.querySelector('.nav-chapters.previous');
|
||||
if (previousButton) {
|
||||
window.location.href = previousButton.href;
|
||||
}
|
||||
break;
|
||||
}
|
||||
});
|
||||
})();
|
||||
|
||||
(function clipboard() {
|
||||
var clipButtons = document.querySelectorAll('.clip-button');
|
||||
|
||||
function hideTooltip(elem) {
|
||||
elem.firstChild.innerText = "";
|
||||
elem.className = 'fa fa-copy clip-button';
|
||||
}
|
||||
|
||||
function showTooltip(elem, msg) {
|
||||
elem.firstChild.innerText = msg;
|
||||
elem.className = 'fa fa-copy tooltipped';
|
||||
}
|
||||
|
||||
var clipboardSnippets = new ClipboardJS('.clip-button', {
|
||||
text: function (trigger) {
|
||||
hideTooltip(trigger);
|
||||
let playground = trigger.closest("pre");
|
||||
return playground_text(playground, false);
|
||||
}
|
||||
});
|
||||
|
||||
Array.from(clipButtons).forEach(function (clipButton) {
|
||||
clipButton.addEventListener('mouseout', function (e) {
|
||||
hideTooltip(e.currentTarget);
|
||||
});
|
||||
});
|
||||
|
||||
clipboardSnippets.on('success', function (e) {
|
||||
e.clearSelection();
|
||||
showTooltip(e.trigger, "Copied!");
|
||||
});
|
||||
|
||||
clipboardSnippets.on('error', function (e) {
|
||||
showTooltip(e.trigger, "Clipboard error!");
|
||||
});
|
||||
})();
|
||||
|
||||
(function scrollToTop () {
|
||||
var menuTitle = document.querySelector('.menu-title');
|
||||
|
||||
menuTitle.addEventListener('click', function () {
|
||||
document.scrollingElement.scrollTo({ top: 0, behavior: 'smooth' });
|
||||
});
|
||||
})();
|
||||
|
||||
(function controllMenu() {
|
||||
var menu = document.getElementById('menu-bar');
|
||||
|
||||
(function controllPosition() {
|
||||
var scrollTop = document.scrollingElement.scrollTop;
|
||||
var prevScrollTop = scrollTop;
|
||||
var minMenuY = -menu.clientHeight - 50;
|
||||
// When the script loads, the page can be at any scroll (e.g. if you reforesh it).
|
||||
menu.style.top = scrollTop + 'px';
|
||||
// Same as parseInt(menu.style.top.slice(0, -2), but faster
|
||||
var topCache = menu.style.top.slice(0, -2);
|
||||
menu.classList.remove('sticky');
|
||||
var stickyCache = false; // Same as menu.classList.contains('sticky'), but faster
|
||||
document.addEventListener('scroll', function () {
|
||||
scrollTop = Math.max(document.scrollingElement.scrollTop, 0);
|
||||
// `null` means that it doesn't need to be updated
|
||||
var nextSticky = null;
|
||||
var nextTop = null;
|
||||
var scrollDown = scrollTop > prevScrollTop;
|
||||
var menuPosAbsoluteY = topCache - scrollTop;
|
||||
if (scrollDown) {
|
||||
nextSticky = false;
|
||||
if (menuPosAbsoluteY > 0) {
|
||||
nextTop = prevScrollTop;
|
||||
}
|
||||
} else {
|
||||
if (menuPosAbsoluteY > 0) {
|
||||
nextSticky = true;
|
||||
} else if (menuPosAbsoluteY < minMenuY) {
|
||||
nextTop = prevScrollTop + minMenuY;
|
||||
}
|
||||
}
|
||||
if (nextSticky === true && stickyCache === false) {
|
||||
menu.classList.add('sticky');
|
||||
stickyCache = true;
|
||||
} else if (nextSticky === false && stickyCache === true) {
|
||||
menu.classList.remove('sticky');
|
||||
stickyCache = false;
|
||||
}
|
||||
if (nextTop !== null) {
|
||||
menu.style.top = nextTop + 'px';
|
||||
topCache = nextTop;
|
||||
}
|
||||
prevScrollTop = scrollTop;
|
||||
}, { passive: true });
|
||||
})();
|
||||
(function controllBorder() {
|
||||
menu.classList.remove('bordered');
|
||||
document.addEventListener('scroll', function () {
|
||||
if (menu.offsetTop === 0) {
|
||||
menu.classList.remove('bordered');
|
||||
} else {
|
||||
menu.classList.add('bordered');
|
||||
}
|
||||
}, { passive: true });
|
||||
})();
|
||||
})();
|
||||
7
clipboard.min.js
vendored
Normal file
545
css/chrome.css
Normal file
@@ -0,0 +1,545 @@
|
||||
/* CSS for UI elements (a.k.a. chrome) */
|
||||
|
||||
@import 'variables.css';
|
||||
|
||||
html {
|
||||
scrollbar-color: var(--scrollbar) var(--bg);
|
||||
}
|
||||
#searchresults a,
|
||||
.content a:link,
|
||||
a:visited,
|
||||
a > .hljs {
|
||||
color: var(--links);
|
||||
}
|
||||
|
||||
/*
|
||||
body-container is necessary because mobile browsers don't seem to like
|
||||
overflow-x on the body tag when there is a <meta name="viewport"> tag.
|
||||
*/
|
||||
#body-container {
|
||||
/*
|
||||
This is used when the sidebar pushes the body content off the side of
|
||||
the screen on small screens. Without it, dragging on mobile Safari
|
||||
will want to reposition the viewport in a weird way.
|
||||
*/
|
||||
overflow-x: clip;
|
||||
}
|
||||
|
||||
/* Menu Bar */
|
||||
|
||||
#menu-bar,
|
||||
#menu-bar-hover-placeholder {
|
||||
z-index: 101;
|
||||
margin: auto calc(0px - var(--page-padding));
|
||||
}
|
||||
#menu-bar {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
background-color: var(--bg);
|
||||
border-bottom-color: var(--bg);
|
||||
border-bottom-width: 1px;
|
||||
border-bottom-style: solid;
|
||||
}
|
||||
#menu-bar.sticky,
|
||||
.js #menu-bar-hover-placeholder:hover + #menu-bar,
|
||||
.js #menu-bar:hover,
|
||||
.js.sidebar-visible #menu-bar {
|
||||
position: -webkit-sticky;
|
||||
position: sticky;
|
||||
top: 0 !important;
|
||||
}
|
||||
#menu-bar-hover-placeholder {
|
||||
position: sticky;
|
||||
position: -webkit-sticky;
|
||||
top: 0;
|
||||
height: var(--menu-bar-height);
|
||||
}
|
||||
#menu-bar.bordered {
|
||||
border-bottom-color: var(--table-border-color);
|
||||
}
|
||||
#menu-bar i, #menu-bar .icon-button {
|
||||
position: relative;
|
||||
padding: 0 8px;
|
||||
z-index: 10;
|
||||
line-height: var(--menu-bar-height);
|
||||
cursor: pointer;
|
||||
transition: color 0.5s;
|
||||
}
|
||||
@media only screen and (max-width: 420px) {
|
||||
#menu-bar i, #menu-bar .icon-button {
|
||||
padding: 0 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.icon-button {
|
||||
border: none;
|
||||
background: none;
|
||||
padding: 0;
|
||||
color: inherit;
|
||||
}
|
||||
.icon-button i {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.right-buttons {
|
||||
margin: 0 15px;
|
||||
}
|
||||
.right-buttons a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.left-buttons {
|
||||
display: flex;
|
||||
margin: 0 5px;
|
||||
}
|
||||
.no-js .left-buttons {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.menu-title {
|
||||
display: inline-block;
|
||||
font-weight: 200;
|
||||
font-size: 2.4rem;
|
||||
line-height: var(--menu-bar-height);
|
||||
text-align: center;
|
||||
margin: 0;
|
||||
flex: 1;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.js .menu-title {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.menu-bar,
|
||||
.menu-bar:visited,
|
||||
.nav-chapters,
|
||||
.nav-chapters:visited,
|
||||
.mobile-nav-chapters,
|
||||
.mobile-nav-chapters:visited,
|
||||
.menu-bar .icon-button,
|
||||
.menu-bar a i {
|
||||
color: var(--icons);
|
||||
}
|
||||
|
||||
.menu-bar i:hover,
|
||||
.menu-bar .icon-button:hover,
|
||||
.nav-chapters:hover,
|
||||
.mobile-nav-chapters i:hover {
|
||||
color: var(--icons-hover);
|
||||
}
|
||||
|
||||
/* Nav Icons */
|
||||
|
||||
.nav-chapters {
|
||||
font-size: 2.5em;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
|
||||
position: fixed;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
margin: 0;
|
||||
max-width: 150px;
|
||||
min-width: 90px;
|
||||
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-content: center;
|
||||
flex-direction: column;
|
||||
|
||||
transition: color 0.5s, background-color 0.5s;
|
||||
}
|
||||
|
||||
.nav-chapters:hover {
|
||||
text-decoration: none;
|
||||
background-color: var(--theme-hover);
|
||||
transition: background-color 0.15s, color 0.15s;
|
||||
}
|
||||
|
||||
.nav-wrapper {
|
||||
margin-top: 50px;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.mobile-nav-chapters {
|
||||
font-size: 2.5em;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
width: 90px;
|
||||
border-radius: 5px;
|
||||
background-color: var(--sidebar-bg);
|
||||
}
|
||||
|
||||
.previous {
|
||||
float: left;
|
||||
}
|
||||
|
||||
.next {
|
||||
float: right;
|
||||
right: var(--page-padding);
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 1080px) {
|
||||
.nav-wide-wrapper { display: none; }
|
||||
.nav-wrapper { display: block; }
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 1380px) {
|
||||
.sidebar-visible .nav-wide-wrapper { display: none; }
|
||||
.sidebar-visible .nav-wrapper { display: block; }
|
||||
}
|
||||
|
||||
/* Inline code */
|
||||
|
||||
:not(pre) > .hljs {
|
||||
display: inline;
|
||||
padding: 0.1em 0.3em;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
:not(pre):not(a) > .hljs {
|
||||
color: var(--inline-code-color);
|
||||
overflow-x: initial;
|
||||
}
|
||||
|
||||
a:hover > .hljs {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
pre {
|
||||
position: relative;
|
||||
}
|
||||
pre > .buttons {
|
||||
position: absolute;
|
||||
z-index: 100;
|
||||
right: 0px;
|
||||
top: 2px;
|
||||
margin: 0px;
|
||||
padding: 2px 0px;
|
||||
|
||||
color: var(--sidebar-fg);
|
||||
cursor: pointer;
|
||||
visibility: hidden;
|
||||
opacity: 0;
|
||||
transition: visibility 0.1s linear, opacity 0.1s linear;
|
||||
}
|
||||
pre:hover > .buttons {
|
||||
visibility: visible;
|
||||
opacity: 1
|
||||
}
|
||||
pre > .buttons :hover {
|
||||
color: var(--sidebar-active);
|
||||
border-color: var(--icons-hover);
|
||||
background-color: var(--theme-hover);
|
||||
}
|
||||
pre > .buttons i {
|
||||
margin-left: 8px;
|
||||
}
|
||||
pre > .buttons button {
|
||||
cursor: inherit;
|
||||
margin: 0px 5px;
|
||||
padding: 3px 5px;
|
||||
font-size: 14px;
|
||||
|
||||
border-style: solid;
|
||||
border-width: 1px;
|
||||
border-radius: 4px;
|
||||
border-color: var(--icons);
|
||||
background-color: var(--theme-popup-bg);
|
||||
transition: 100ms;
|
||||
transition-property: color,border-color,background-color;
|
||||
color: var(--icons);
|
||||
}
|
||||
@media (pointer: coarse) {
|
||||
pre > .buttons button {
|
||||
/* On mobile, make it easier to tap buttons. */
|
||||
padding: 0.3rem 1rem;
|
||||
}
|
||||
}
|
||||
pre > code {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
/* FIXME: ACE editors overlap their buttons because ACE does absolute
|
||||
positioning within the code block which breaks padding. The only solution I
|
||||
can think of is to move the padding to the outer pre tag (or insert a div
|
||||
wrapper), but that would require fixing a whole bunch of CSS rules.
|
||||
*/
|
||||
.hljs.ace_editor {
|
||||
padding: 0rem 0rem;
|
||||
}
|
||||
|
||||
pre > .result {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
/* Search */
|
||||
|
||||
#searchresults a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
mark {
|
||||
border-radius: 2px;
|
||||
padding: 0 3px 1px 3px;
|
||||
margin: 0 -3px -1px -3px;
|
||||
background-color: var(--search-mark-bg);
|
||||
transition: background-color 300ms linear;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
mark.fade-out {
|
||||
background-color: rgba(0,0,0,0) !important;
|
||||
cursor: auto;
|
||||
}
|
||||
|
||||
.searchbar-outer {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
max-width: var(--content-max-width);
|
||||
}
|
||||
|
||||
#searchbar {
|
||||
width: 100%;
|
||||
margin: 5px auto 0px auto;
|
||||
padding: 10px 16px;
|
||||
transition: box-shadow 300ms ease-in-out;
|
||||
border: 1px solid var(--searchbar-border-color);
|
||||
border-radius: 3px;
|
||||
background-color: var(--searchbar-bg);
|
||||
color: var(--searchbar-fg);
|
||||
}
|
||||
#searchbar:focus,
|
||||
#searchbar.active {
|
||||
box-shadow: 0 0 3px var(--searchbar-shadow-color);
|
||||
}
|
||||
|
||||
.searchresults-header {
|
||||
font-weight: bold;
|
||||
font-size: 1em;
|
||||
padding: 18px 0 0 5px;
|
||||
color: var(--searchresults-header-fg);
|
||||
}
|
||||
|
||||
.searchresults-outer {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
max-width: var(--content-max-width);
|
||||
border-bottom: 1px dashed var(--searchresults-border-color);
|
||||
}
|
||||
|
||||
ul#searchresults {
|
||||
list-style: none;
|
||||
padding-left: 20px;
|
||||
}
|
||||
ul#searchresults li {
|
||||
margin: 10px 0px;
|
||||
padding: 2px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
ul#searchresults li.focus {
|
||||
background-color: var(--searchresults-li-bg);
|
||||
}
|
||||
ul#searchresults span.teaser {
|
||||
display: block;
|
||||
clear: both;
|
||||
margin: 5px 0 0 20px;
|
||||
font-size: 0.8em;
|
||||
}
|
||||
ul#searchresults span.teaser em {
|
||||
font-weight: bold;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
/* Sidebar */
|
||||
|
||||
.sidebar {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
width: var(--sidebar-width);
|
||||
font-size: 0.875em;
|
||||
box-sizing: border-box;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
overscroll-behavior-y: contain;
|
||||
background-color: var(--sidebar-bg);
|
||||
color: var(--sidebar-fg);
|
||||
}
|
||||
.sidebar-resizing {
|
||||
-moz-user-select: none;
|
||||
-webkit-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
.js:not(.sidebar-resizing) .sidebar {
|
||||
transition: transform 0.3s; /* Animation: slide away */
|
||||
}
|
||||
.sidebar code {
|
||||
line-height: 2em;
|
||||
}
|
||||
.sidebar .sidebar-scrollbox {
|
||||
overflow-y: auto;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
padding: 10px 10px;
|
||||
}
|
||||
.sidebar .sidebar-resize-handle {
|
||||
position: absolute;
|
||||
cursor: col-resize;
|
||||
width: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
.js .sidebar .sidebar-resize-handle {
|
||||
cursor: col-resize;
|
||||
width: 5px;
|
||||
}
|
||||
.sidebar-hidden .sidebar {
|
||||
transform: translateX(calc(0px - var(--sidebar-width)));
|
||||
}
|
||||
.sidebar::-webkit-scrollbar {
|
||||
background: var(--sidebar-bg);
|
||||
}
|
||||
.sidebar::-webkit-scrollbar-thumb {
|
||||
background: var(--scrollbar);
|
||||
}
|
||||
|
||||
.sidebar-visible .page-wrapper {
|
||||
transform: translateX(var(--sidebar-width));
|
||||
}
|
||||
@media only screen and (min-width: 620px) {
|
||||
.sidebar-visible .page-wrapper {
|
||||
transform: none;
|
||||
margin-left: var(--sidebar-width);
|
||||
}
|
||||
}
|
||||
|
||||
.chapter {
|
||||
list-style: none outside none;
|
||||
padding-left: 0;
|
||||
line-height: 2.2em;
|
||||
}
|
||||
|
||||
.chapter ol {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.chapter li {
|
||||
display: flex;
|
||||
color: var(--sidebar-non-existant);
|
||||
}
|
||||
.chapter li a {
|
||||
display: block;
|
||||
padding: 0;
|
||||
text-decoration: none;
|
||||
color: var(--sidebar-fg);
|
||||
}
|
||||
|
||||
.chapter li a:hover {
|
||||
color: var(--sidebar-active);
|
||||
}
|
||||
|
||||
.chapter li a.active {
|
||||
color: var(--sidebar-active);
|
||||
}
|
||||
|
||||
.chapter li > a.toggle {
|
||||
cursor: pointer;
|
||||
display: block;
|
||||
margin-left: auto;
|
||||
padding: 0 10px;
|
||||
user-select: none;
|
||||
opacity: 0.68;
|
||||
}
|
||||
|
||||
.chapter li > a.toggle div {
|
||||
transition: transform 0.5s;
|
||||
}
|
||||
|
||||
/* collapse the section */
|
||||
.chapter li:not(.expanded) + li > ol {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.chapter li.chapter-item {
|
||||
line-height: 1.5em;
|
||||
margin-top: 0.6em;
|
||||
}
|
||||
|
||||
.chapter li.expanded > a.toggle div {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
.spacer {
|
||||
width: 100%;
|
||||
height: 3px;
|
||||
margin: 5px 0px;
|
||||
}
|
||||
.chapter .spacer {
|
||||
background-color: var(--sidebar-spacer);
|
||||
}
|
||||
|
||||
@media (-moz-touch-enabled: 1), (pointer: coarse) {
|
||||
.chapter li a { padding: 5px 0; }
|
||||
.spacer { margin: 10px 0; }
|
||||
}
|
||||
|
||||
.section {
|
||||
list-style: none outside none;
|
||||
padding-left: 20px;
|
||||
line-height: 1.9em;
|
||||
}
|
||||
|
||||
/* Theme Menu Popup */
|
||||
|
||||
.theme-popup {
|
||||
position: absolute;
|
||||
left: 10px;
|
||||
top: var(--menu-bar-height);
|
||||
z-index: 1000;
|
||||
border-radius: 4px;
|
||||
font-size: 0.7em;
|
||||
color: var(--fg);
|
||||
background: var(--theme-popup-bg);
|
||||
border: 1px solid var(--theme-popup-border);
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
display: none;
|
||||
/* Don't let the children's background extend past the rounded corners. */
|
||||
overflow: hidden;
|
||||
}
|
||||
.theme-popup .default {
|
||||
color: var(--icons);
|
||||
}
|
||||
.theme-popup .theme {
|
||||
width: 100%;
|
||||
border: 0;
|
||||
margin: 0;
|
||||
padding: 2px 20px;
|
||||
line-height: 25px;
|
||||
white-space: nowrap;
|
||||
text-align: left;
|
||||
cursor: pointer;
|
||||
color: inherit;
|
||||
background: inherit;
|
||||
font-size: inherit;
|
||||
}
|
||||
.theme-popup .theme:hover {
|
||||
background-color: var(--theme-hover);
|
||||
}
|
||||
|
||||
.theme-selected::before {
|
||||
display: inline-block;
|
||||
content: "✓";
|
||||
margin-left: -14px;
|
||||
width: 14px;
|
||||
}
|
||||
203
css/general.css
Normal file
@@ -0,0 +1,203 @@
|
||||
/* Base styles and content styles */
|
||||
|
||||
@import 'variables.css';
|
||||
|
||||
:root {
|
||||
/* Browser default font-size is 16px, this way 1 rem = 10px */
|
||||
font-size: 62.5%;
|
||||
}
|
||||
|
||||
html {
|
||||
font-family: "Open Sans", sans-serif;
|
||||
color: var(--fg);
|
||||
background-color: var(--bg);
|
||||
text-size-adjust: none;
|
||||
-webkit-text-size-adjust: none;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
font-size: 1.6rem;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
code {
|
||||
font-family: var(--mono-font) !important;
|
||||
font-size: var(--code-font-size);
|
||||
}
|
||||
|
||||
/* make long words/inline code not x overflow */
|
||||
main {
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
|
||||
/* make wide tables scroll if they overflow */
|
||||
.table-wrapper {
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
/* Don't change font size in headers. */
|
||||
h1 code, h2 code, h3 code, h4 code, h5 code, h6 code {
|
||||
font-size: unset;
|
||||
}
|
||||
|
||||
.left { float: left; }
|
||||
.right { float: right; }
|
||||
.boring { opacity: 0.6; }
|
||||
.hide-boring .boring { display: none; }
|
||||
.hidden { display: none !important; }
|
||||
|
||||
h2, h3 { margin-top: 2.5em; }
|
||||
h4, h5 { margin-top: 2em; }
|
||||
|
||||
.header + .header h3,
|
||||
.header + .header h4,
|
||||
.header + .header h5 {
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
||||
h1:target::before,
|
||||
h2:target::before,
|
||||
h3:target::before,
|
||||
h4:target::before,
|
||||
h5:target::before,
|
||||
h6:target::before {
|
||||
display: inline-block;
|
||||
content: "»";
|
||||
margin-left: -30px;
|
||||
width: 30px;
|
||||
}
|
||||
|
||||
/* This is broken on Safari as of version 14, but is fixed
|
||||
in Safari Technology Preview 117 which I think will be Safari 14.2.
|
||||
https://bugs.webkit.org/show_bug.cgi?id=218076
|
||||
*/
|
||||
:target {
|
||||
scroll-margin-top: calc(var(--menu-bar-height) + 0.5em);
|
||||
}
|
||||
|
||||
.page {
|
||||
outline: 0;
|
||||
padding: 0 var(--page-padding);
|
||||
margin-top: calc(0px - var(--menu-bar-height)); /* Compensate for the #menu-bar-hover-placeholder */
|
||||
}
|
||||
.page-wrapper {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.js:not(.sidebar-resizing) .page-wrapper {
|
||||
transition: margin-left 0.3s ease, transform 0.3s ease; /* Animation: slide away */
|
||||
}
|
||||
|
||||
.content {
|
||||
overflow-y: auto;
|
||||
padding: 0 5px 50px 5px;
|
||||
}
|
||||
.content main {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
max-width: var(--content-max-width);
|
||||
}
|
||||
.content p { line-height: 1.45em; }
|
||||
.content ol { line-height: 1.45em; }
|
||||
.content ul { line-height: 1.45em; }
|
||||
.content a { text-decoration: none; }
|
||||
.content a:hover { text-decoration: underline; }
|
||||
.content img, .content video { max-width: 100%; }
|
||||
.content .header:link,
|
||||
.content .header:visited {
|
||||
color: var(--fg);
|
||||
}
|
||||
.content .header:link,
|
||||
.content .header:visited:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
table {
|
||||
margin: 0 auto;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
table td {
|
||||
padding: 3px 20px;
|
||||
border: 1px var(--table-border-color) solid;
|
||||
}
|
||||
table thead {
|
||||
background: var(--table-header-bg);
|
||||
}
|
||||
table thead td {
|
||||
font-weight: 700;
|
||||
border: none;
|
||||
}
|
||||
table thead th {
|
||||
padding: 3px 20px;
|
||||
}
|
||||
table thead tr {
|
||||
border: 1px var(--table-header-bg) solid;
|
||||
}
|
||||
/* Alternate background colors for rows */
|
||||
table tbody tr:nth-child(2n) {
|
||||
background: var(--table-alternate-bg);
|
||||
}
|
||||
|
||||
|
||||
blockquote {
|
||||
margin: 20px 0;
|
||||
padding: 0 20px;
|
||||
color: var(--fg);
|
||||
background-color: var(--quote-bg);
|
||||
border-top: .1em solid var(--quote-border);
|
||||
border-bottom: .1em solid var(--quote-border);
|
||||
}
|
||||
|
||||
kbd {
|
||||
background-color: var(--table-border-color);
|
||||
border-radius: 4px;
|
||||
border: solid 1px var(--theme-popup-border);
|
||||
box-shadow: inset 0 -1px 0 var(--theme-hover);
|
||||
display: inline-block;
|
||||
font-size: var(--code-font-size);
|
||||
font-family: var(--mono-font);
|
||||
line-height: 10px;
|
||||
padding: 4px 5px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
:not(.footnote-definition) + .footnote-definition,
|
||||
.footnote-definition + :not(.footnote-definition) {
|
||||
margin-top: 2em;
|
||||
}
|
||||
.footnote-definition {
|
||||
font-size: 0.9em;
|
||||
margin: 0.5em 0;
|
||||
}
|
||||
.footnote-definition p {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.tooltiptext {
|
||||
position: absolute;
|
||||
visibility: hidden;
|
||||
color: #fff;
|
||||
background-color: #333;
|
||||
transform: translateX(-50%); /* Center by moving tooltip 50% of its width left */
|
||||
left: -8px; /* Half of the width of the icon */
|
||||
top: -35px;
|
||||
font-size: 0.8em;
|
||||
text-align: center;
|
||||
border-radius: 6px;
|
||||
padding: 5px 8px;
|
||||
margin: 5px;
|
||||
z-index: 1000;
|
||||
}
|
||||
.tooltipped .tooltiptext {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.chapter li.part-title {
|
||||
color: var(--sidebar-fg);
|
||||
margin: 5px 0px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.result-no-output {
|
||||
font-style: italic;
|
||||
}
|
||||
54
css/print.css
Normal file
@@ -0,0 +1,54 @@
|
||||
|
||||
#sidebar,
|
||||
#menu-bar,
|
||||
.nav-chapters,
|
||||
.mobile-nav-chapters {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#page-wrapper.page-wrapper {
|
||||
transform: none;
|
||||
margin-left: 0px;
|
||||
overflow-y: initial;
|
||||
}
|
||||
|
||||
#content {
|
||||
max-width: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.page {
|
||||
overflow-y: initial;
|
||||
}
|
||||
|
||||
code {
|
||||
background-color: #666666;
|
||||
border-radius: 5px;
|
||||
|
||||
/* Force background to be printed in Chrome */
|
||||
-webkit-print-color-adjust: exact;
|
||||
}
|
||||
|
||||
pre > .buttons {
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
a, a:visited, a:active, a:hover {
|
||||
color: #4183c4;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
page-break-inside: avoid;
|
||||
page-break-after: avoid;
|
||||
}
|
||||
|
||||
pre, code {
|
||||
page-break-inside: avoid;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
.fa {
|
||||
display: none !important;
|
||||
}
|
||||
255
css/variables.css
Normal file
@@ -0,0 +1,255 @@
|
||||
|
||||
/* Globals */
|
||||
|
||||
:root {
|
||||
--sidebar-width: 300px;
|
||||
--page-padding: 15px;
|
||||
--content-max-width: 750px;
|
||||
--menu-bar-height: 50px;
|
||||
--mono-font: "Source Code Pro", Consolas, "Ubuntu Mono", Menlo, "DejaVu Sans Mono", monospace, monospace;
|
||||
--code-font-size: 0.875em /* please adjust the ace font size accordingly in editor.js */
|
||||
}
|
||||
|
||||
/* Themes */
|
||||
|
||||
.ayu {
|
||||
--bg: hsl(210, 25%, 8%);
|
||||
--fg: #c5c5c5;
|
||||
|
||||
--sidebar-bg: #14191f;
|
||||
--sidebar-fg: #c8c9db;
|
||||
--sidebar-non-existant: #5c6773;
|
||||
--sidebar-active: #ffb454;
|
||||
--sidebar-spacer: #2d334f;
|
||||
|
||||
--scrollbar: var(--sidebar-fg);
|
||||
|
||||
--icons: #737480;
|
||||
--icons-hover: #b7b9cc;
|
||||
|
||||
--links: #0096cf;
|
||||
|
||||
--inline-code-color: #ffb454;
|
||||
|
||||
--theme-popup-bg: #14191f;
|
||||
--theme-popup-border: #5c6773;
|
||||
--theme-hover: #191f26;
|
||||
|
||||
--quote-bg: hsl(226, 15%, 17%);
|
||||
--quote-border: hsl(226, 15%, 22%);
|
||||
|
||||
--table-border-color: hsl(210, 25%, 13%);
|
||||
--table-header-bg: hsl(210, 25%, 28%);
|
||||
--table-alternate-bg: hsl(210, 25%, 11%);
|
||||
|
||||
--searchbar-border-color: #848484;
|
||||
--searchbar-bg: #424242;
|
||||
--searchbar-fg: #fff;
|
||||
--searchbar-shadow-color: #d4c89f;
|
||||
--searchresults-header-fg: #666;
|
||||
--searchresults-border-color: #888;
|
||||
--searchresults-li-bg: #252932;
|
||||
--search-mark-bg: #e3b171;
|
||||
}
|
||||
|
||||
.coal {
|
||||
--bg: hsl(200, 7%, 8%);
|
||||
--fg: #98a3ad;
|
||||
|
||||
--sidebar-bg: #292c2f;
|
||||
--sidebar-fg: #a1adb8;
|
||||
--sidebar-non-existant: #505254;
|
||||
--sidebar-active: #3473ad;
|
||||
--sidebar-spacer: #393939;
|
||||
|
||||
--scrollbar: var(--sidebar-fg);
|
||||
|
||||
--icons: #43484d;
|
||||
--icons-hover: #b3c0cc;
|
||||
|
||||
--links: #2b79a2;
|
||||
|
||||
--inline-code-color: #c5c8c6;
|
||||
|
||||
--theme-popup-bg: #141617;
|
||||
--theme-popup-border: #43484d;
|
||||
--theme-hover: #1f2124;
|
||||
|
||||
--quote-bg: hsl(234, 21%, 18%);
|
||||
--quote-border: hsl(234, 21%, 23%);
|
||||
|
||||
--table-border-color: hsl(200, 7%, 13%);
|
||||
--table-header-bg: hsl(200, 7%, 28%);
|
||||
--table-alternate-bg: hsl(200, 7%, 11%);
|
||||
|
||||
--searchbar-border-color: #aaa;
|
||||
--searchbar-bg: #b7b7b7;
|
||||
--searchbar-fg: #000;
|
||||
--searchbar-shadow-color: #aaa;
|
||||
--searchresults-header-fg: #666;
|
||||
--searchresults-border-color: #98a3ad;
|
||||
--searchresults-li-bg: #2b2b2f;
|
||||
--search-mark-bg: #355c7d;
|
||||
}
|
||||
|
||||
.light {
|
||||
--bg: hsl(0, 0%, 100%);
|
||||
--fg: hsl(0, 0%, 0%);
|
||||
|
||||
--sidebar-bg: #fafafa;
|
||||
--sidebar-fg: hsl(0, 0%, 0%);
|
||||
--sidebar-non-existant: #aaaaaa;
|
||||
--sidebar-active: #1f1fff;
|
||||
--sidebar-spacer: #f4f4f4;
|
||||
|
||||
--scrollbar: #8F8F8F;
|
||||
|
||||
--icons: #747474;
|
||||
--icons-hover: #000000;
|
||||
|
||||
--links: #20609f;
|
||||
|
||||
--inline-code-color: #301900;
|
||||
|
||||
--theme-popup-bg: #fafafa;
|
||||
--theme-popup-border: #cccccc;
|
||||
--theme-hover: #e6e6e6;
|
||||
|
||||
--quote-bg: hsl(197, 37%, 96%);
|
||||
--quote-border: hsl(197, 37%, 91%);
|
||||
|
||||
--table-border-color: hsl(0, 0%, 95%);
|
||||
--table-header-bg: hsl(0, 0%, 80%);
|
||||
--table-alternate-bg: hsl(0, 0%, 97%);
|
||||
|
||||
--searchbar-border-color: #aaa;
|
||||
--searchbar-bg: #fafafa;
|
||||
--searchbar-fg: #000;
|
||||
--searchbar-shadow-color: #aaa;
|
||||
--searchresults-header-fg: #666;
|
||||
--searchresults-border-color: #888;
|
||||
--searchresults-li-bg: #e4f2fe;
|
||||
--search-mark-bg: #a2cff5;
|
||||
}
|
||||
|
||||
.navy {
|
||||
--bg: hsl(226, 23%, 11%);
|
||||
--fg: #bcbdd0;
|
||||
|
||||
--sidebar-bg: #282d3f;
|
||||
--sidebar-fg: #c8c9db;
|
||||
--sidebar-non-existant: #505274;
|
||||
--sidebar-active: #2b79a2;
|
||||
--sidebar-spacer: #2d334f;
|
||||
|
||||
--scrollbar: var(--sidebar-fg);
|
||||
|
||||
--icons: #737480;
|
||||
--icons-hover: #b7b9cc;
|
||||
|
||||
--links: #2b79a2;
|
||||
|
||||
--inline-code-color: #c5c8c6;
|
||||
|
||||
--theme-popup-bg: #161923;
|
||||
--theme-popup-border: #737480;
|
||||
--theme-hover: #282e40;
|
||||
|
||||
--quote-bg: hsl(226, 15%, 17%);
|
||||
--quote-border: hsl(226, 15%, 22%);
|
||||
|
||||
--table-border-color: hsl(226, 23%, 16%);
|
||||
--table-header-bg: hsl(226, 23%, 31%);
|
||||
--table-alternate-bg: hsl(226, 23%, 14%);
|
||||
|
||||
--searchbar-border-color: #aaa;
|
||||
--searchbar-bg: #aeaec6;
|
||||
--searchbar-fg: #000;
|
||||
--searchbar-shadow-color: #aaa;
|
||||
--searchresults-header-fg: #5f5f71;
|
||||
--searchresults-border-color: #5c5c68;
|
||||
--searchresults-li-bg: #242430;
|
||||
--search-mark-bg: #a2cff5;
|
||||
}
|
||||
|
||||
.rust {
|
||||
--bg: hsl(60, 9%, 87%);
|
||||
--fg: #262625;
|
||||
|
||||
--sidebar-bg: #3b2e2a;
|
||||
--sidebar-fg: #c8c9db;
|
||||
--sidebar-non-existant: #505254;
|
||||
--sidebar-active: #e69f67;
|
||||
--sidebar-spacer: #45373a;
|
||||
|
||||
--scrollbar: var(--sidebar-fg);
|
||||
|
||||
--icons: #737480;
|
||||
--icons-hover: #262625;
|
||||
|
||||
--links: #2b79a2;
|
||||
|
||||
--inline-code-color: #6e6b5e;
|
||||
|
||||
--theme-popup-bg: #e1e1db;
|
||||
--theme-popup-border: #b38f6b;
|
||||
--theme-hover: #99908a;
|
||||
|
||||
--quote-bg: hsl(60, 5%, 75%);
|
||||
--quote-border: hsl(60, 5%, 70%);
|
||||
|
||||
--table-border-color: hsl(60, 9%, 82%);
|
||||
--table-header-bg: #b3a497;
|
||||
--table-alternate-bg: hsl(60, 9%, 84%);
|
||||
|
||||
--searchbar-border-color: #aaa;
|
||||
--searchbar-bg: #fafafa;
|
||||
--searchbar-fg: #000;
|
||||
--searchbar-shadow-color: #aaa;
|
||||
--searchresults-header-fg: #666;
|
||||
--searchresults-border-color: #888;
|
||||
--searchresults-li-bg: #dec2a2;
|
||||
--search-mark-bg: #e69f67;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.light.no-js {
|
||||
--bg: hsl(200, 7%, 8%);
|
||||
--fg: #98a3ad;
|
||||
|
||||
--sidebar-bg: #292c2f;
|
||||
--sidebar-fg: #a1adb8;
|
||||
--sidebar-non-existant: #505254;
|
||||
--sidebar-active: #3473ad;
|
||||
--sidebar-spacer: #393939;
|
||||
|
||||
--scrollbar: var(--sidebar-fg);
|
||||
|
||||
--icons: #43484d;
|
||||
--icons-hover: #b3c0cc;
|
||||
|
||||
--links: #2b79a2;
|
||||
|
||||
--inline-code-color: #c5c8c6;
|
||||
|
||||
--theme-popup-bg: #141617;
|
||||
--theme-popup-border: #43484d;
|
||||
--theme-hover: #1f2124;
|
||||
|
||||
--quote-bg: hsl(234, 21%, 18%);
|
||||
--quote-border: hsl(234, 21%, 23%);
|
||||
|
||||
--table-border-color: hsl(200, 7%, 13%);
|
||||
--table-header-bg: hsl(200, 7%, 28%);
|
||||
--table-alternate-bg: hsl(200, 7%, 11%);
|
||||
|
||||
--searchbar-border-color: #aaa;
|
||||
--searchbar-bg: #b7b7b7;
|
||||
--searchbar-fg: #000;
|
||||
--searchbar-shadow-color: #aaa;
|
||||
--searchresults-header-fg: #666;
|
||||
--searchresults-border-color: #98a3ad;
|
||||
--searchresults-li-bg: #2b2b2f;
|
||||
--search-mark-bg: #355c7d;
|
||||
}
|
||||
}
|
||||
10
elasticlunr.min.js
vendored
Normal file
BIN
favicon.png
Normal file
|
After Width: | Height: | Size: 5.5 KiB |
22
favicon.svg
Normal file
@@ -0,0 +1,22 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 199.7 184.2">
|
||||
<style>
|
||||
@media (prefers-color-scheme: dark) {
|
||||
svg { fill: white; }
|
||||
}
|
||||
</style>
|
||||
<path d="M189.5,36.8c0.2,2.8,0,5.1-0.6,6.8L153,162c-0.6,2.1-2,3.7-4.2,5c-2.2,1.2-4.4,1.9-6.7,1.9H31.4c-9.6,0-15.3-2.8-17.3-8.4
|
||||
c-0.8-2.2-0.8-3.9,0.1-5.2c0.9-1.2,2.4-1.8,4.6-1.8H123c7.4,0,12.6-1.4,15.4-4.1s5.7-8.9,8.6-18.4l32.9-108.6
|
||||
c1.8-5.9,1-11.1-2.2-15.6S169.9,0,164,0H72.7c-1,0-3.1,0.4-6.1,1.1l0.1-0.4C64.5,0.2,62.6,0,61,0.1s-3,0.5-4.3,1.4
|
||||
c-1.3,0.9-2.4,1.8-3.2,2.8S52,6.5,51.2,8.1c-0.8,1.6-1.4,3-1.9,4.3s-1.1,2.7-1.8,4.2c-0.7,1.5-1.3,2.7-2,3.7c-0.5,0.6-1.2,1.5-2,2.5
|
||||
s-1.6,2-2.2,2.8s-0.9,1.5-1.1,2.2c-0.2,0.7-0.1,1.8,0.2,3.2c0.3,1.4,0.4,2.4,0.4,3.1c-0.3,3-1.4,6.9-3.3,11.6
|
||||
c-1.9,4.7-3.6,8.1-5.1,10.1c-0.3,0.4-1.2,1.3-2.6,2.7c-1.4,1.4-2.3,2.6-2.6,3.7c-0.3,0.4-0.3,1.5-0.1,3.4c0.3,1.8,0.4,3.1,0.3,3.8
|
||||
c-0.3,2.7-1.3,6.3-3,10.8c-1.7,4.5-3.4,8.2-5,11c-0.2,0.5-0.9,1.4-2,2.8c-1.1,1.4-1.8,2.5-2,3.4c-0.2,0.6-0.1,1.8,0.1,3.4
|
||||
c0.2,1.6,0.2,2.8-0.1,3.6c-0.6,3-1.8,6.7-3.6,11c-1.8,4.3-3.6,7.9-5.4,11c-0.5,0.8-1.1,1.7-2,2.8c-0.8,1.1-1.5,2-2,2.8
|
||||
s-0.8,1.6-1,2.5c-0.1,0.5,0,1.3,0.4,2.3c0.3,1.1,0.4,1.9,0.4,2.6c-0.1,1.1-0.2,2.6-0.5,4.4c-0.2,1.8-0.4,2.9-0.4,3.2
|
||||
c-1.8,4.8-1.7,9.9,0.2,15.2c2.2,6.2,6.2,11.5,11.9,15.8c5.7,4.3,11.7,6.4,17.8,6.4h110.7c5.2,0,10.1-1.7,14.7-5.2s7.7-7.8,9.2-12.9
|
||||
l33-108.6c1.8-5.8,1-10.9-2.2-15.5C194.9,39.7,192.6,38,189.5,36.8z M59.6,122.8L73.8,80c0,0,7,0,10.8,0s28.8-1.7,25.4,17.5
|
||||
c-3.4,19.2-18.8,25.2-36.8,25.4S59.6,122.8,59.6,122.8z M78.6,116.8c4.7-0.1,18.9-2.9,22.1-17.1S89.2,86.3,89.2,86.3l-8.9,0
|
||||
l-10.2,30.5C70.2,116.9,74,116.9,78.6,116.8z M75.3,68.7L89,26.2h9.8l0.8,34l23.6-34h9.9l-13.6,42.5h-7.1l12.5-35.4l-24.5,35.4h-6.8
|
||||
l-0.8-35L82,68.7H75.3z"/>
|
||||
</svg>
|
||||
<!-- Original image Copyright Dave Gandy — CC BY 4.0 License -->
|
||||
|
After Width: | Height: | Size: 1.8 KiB |
202
fonts/OPEN-SANS-LICENSE.txt
Normal file
@@ -0,0 +1,202 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
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.
|
||||
93
fonts/SOURCE-CODE-PRO-LICENSE.txt
Normal file
@@ -0,0 +1,93 @@
|
||||
Copyright 2010, 2012 Adobe Systems Incorporated (http://www.adobe.com/), with Reserved Font Name 'Source'. All Rights Reserved. Source is a trademark of Adobe Systems Incorporated in the United States and/or other countries.
|
||||
|
||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
http://scripts.sil.org/OFL
|
||||
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font creation
|
||||
efforts of academic and linguistic communities, and to provide a free and
|
||||
open framework in which fonts may be shared and improved in partnership
|
||||
with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply
|
||||
to any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software components as
|
||||
distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||
or substituting -- in part or in whole -- any of the components of the
|
||||
Original Version, by changing formats or by porting the Font Software to a
|
||||
new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||
redistribute, and sell modified and unmodified copies of the Font
|
||||
Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components,
|
||||
in Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the corresponding
|
||||
Copyright Holder. This restriction only applies to the primary font name as
|
||||
presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created
|
||||
using the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||