You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

775 lines
22KB

  1. #![allow(unused)]
  2. use std::thread::{self, JoinHandle};
  3. use std::sync::mpsc::{Sender, channel};
  4. use std::fmt;
  5. use std::time::{Instant, Duration};
  6. use chrono::{self, DateTime, Utc};
  7. use pub_sub::PubSub;
  8. use sloggers::types::Severity;
  9. //use windows::{DurationWindow, Incremental, Window};
  10. use money::{Ticker, Side, Exchange};
  11. use super::file_logger;
  12. use influx::{self, OwnedMeasurement, OwnedValue};
  13. use self::windows::Incremental;
  14. pub type Nanos = u64;
  15. pub const SECOND: u64 = 1e9 as u64;
  16. pub const MINUTE: u64 = SECOND * 60;
  17. pub const HOUR: u64 = MINUTE * 60;
  18. pub const MILLISECOND: u64 = SECOND / 1000;
  19. pub const MICROSECOND: u64 = MILLISECOND / 1000;
  20. pub fn nanos(d: Duration) -> Nanos {
  21. d.as_secs() * 1_000_000_000 + (d.subsec_nanos() as u64)
  22. }
  23. pub fn dt_nanos(t: DateTime<Utc>) -> i64 {
  24. (t.timestamp() as i64) * 1_000_000_000_i64 + (t.timestamp_subsec_nanos() as i64)
  25. }
  26. pub fn now() -> i64 { dt_nanos(Utc::now()) }
  27. pub fn tfmt(ns: Nanos) -> String {
  28. match ns {
  29. t if t <= MICROSECOND => {
  30. format!("{}ns", t)
  31. }
  32. t if t > MICROSECOND && t < MILLISECOND => {
  33. format!("{}u", t / MICROSECOND)
  34. }
  35. t if t > MILLISECOND && t < SECOND => {
  36. format!("{}ms", t / MILLISECOND)
  37. }
  38. t => {
  39. format!("{}.{}sec", t / SECOND, t / MILLISECOND)
  40. }
  41. }
  42. }
  43. pub fn tfmt_dur(d: Duration) -> String {
  44. tfmt(nanos(d))
  45. }
  46. pub fn tfmt_dt(dt: DateTime<Utc>) -> String {
  47. Utc::now().signed_duration_since(dt)
  48. .to_std()
  49. .map(|dur| {
  50. tfmt_dur(dur)
  51. }).unwrap_or("?".into())
  52. }
  53. pub fn tfmt_write(ns: Nanos, f: &mut fmt::Formatter) -> fmt::Result {
  54. match ns {
  55. t if t <= MICROSECOND => {
  56. write!(f, "{}ns", t)
  57. }
  58. t if t > MICROSECOND && t < MILLISECOND => {
  59. write!(f, "{}u", t / MICROSECOND)
  60. }
  61. t if t > MILLISECOND && t < SECOND => {
  62. write!(f, "{}ms", t / MILLISECOND)
  63. }
  64. t => {
  65. write!(f, "{}.{}sec", t / SECOND, t / MILLISECOND)
  66. }
  67. }
  68. }
  69. #[doc(hide)]
  70. mod windows {
  71. use super::*;
  72. use std::ops::{Div, Mul, Sub, SubAssign, AddAssign};
  73. use std::collections::VecDeque;
  74. use num::Float;
  75. const INITIAL_CAPACITY: usize = 1000;
  76. #[derive(Clone, Debug)]
  77. pub struct Point<T>
  78. //where T: Default
  79. {
  80. time: Instant,
  81. value: T
  82. }
  83. #[derive(Debug, Clone)]
  84. pub struct Window<T>
  85. where T: Default
  86. {
  87. pub size: Duration, // window size
  88. mean: T,
  89. ps: T,
  90. psa: T,
  91. var: T,
  92. sum: T,
  93. count: u32,
  94. items: VecDeque<Point<T>>,
  95. }
  96. #[derive(Default)]
  97. pub struct DurationWindow {
  98. pub size: Duration,
  99. mean: Duration,
  100. sum: Duration,
  101. count: u32,
  102. items: VecDeque<Point<Duration>>
  103. }
  104. impl<T> Point<T>
  105. where T: Default + Copy
  106. {
  107. fn new(time: Instant, value: T) -> Self {
  108. Point { time, value }
  109. }
  110. fn value(&self) -> T {
  111. self.value
  112. }
  113. }
  114. impl<T> Window<T>
  115. where T: Default + Zero
  116. {
  117. pub fn new(size: Duration) -> Self {
  118. Window {
  119. size,
  120. mean: T::default(),
  121. psa: T::default(),
  122. ps: T::default(),
  123. sum: T::default(),
  124. count: 0,
  125. var: T::default(),
  126. items: VecDeque::with_capacity(INITIAL_CAPACITY),
  127. }
  128. }
  129. pub fn with_size_and_capacity(size: Duration, capacity: usize) -> Self {
  130. Window {
  131. size,
  132. mean: T::default(),
  133. psa: T::default(),
  134. ps: T::default(),
  135. sum: T::default(),
  136. count: 0,
  137. var: T::default(),
  138. items: VecDeque::with_capacity(capacity),
  139. }
  140. }
  141. }
  142. impl<T> From<Duration> for Window<T>
  143. where T: Default + Zero
  144. {
  145. fn from(size: Duration) -> Self {
  146. Window::new(size)
  147. }
  148. }
  149. impl From<Duration> for DurationWindow {
  150. fn from(size: Duration) -> Self {
  151. DurationWindow::new(size)
  152. }
  153. }
  154. pub trait Incremental<T> {
  155. /// Purge expired items.
  156. ///
  157. #[inline]
  158. fn refresh(&mut self, t: Instant) -> &Self;
  159. /// Add a new item.
  160. ///
  161. #[inline]
  162. fn add(&mut self, time: Instant, value: T);
  163. /// Add a new item and purge expired items.
  164. ///
  165. #[inline]
  166. fn update(&mut self, time: Instant, value: T) {
  167. self.refresh(time);
  168. self.add(time, value);
  169. }
  170. }
  171. pub trait Zero {
  172. fn zero() -> Self;
  173. }
  174. pub trait One {
  175. fn one() -> Self;
  176. }
  177. macro_rules! zero {
  178. ($t:ty, $body:expr) => {
  179. impl Zero for $t {
  180. fn zero() -> $t { $body }
  181. }
  182. }
  183. }
  184. macro_rules! one {
  185. ($t:ty, $body:expr) => {
  186. impl One for $t {
  187. fn one() -> $t { $body }
  188. }
  189. }
  190. }
  191. zero!(f64, 0.0);
  192. zero!(f32, 0.0);
  193. zero!(u128, 0);
  194. zero!(i128, 0);
  195. zero!(u64, 0);
  196. zero!(i64, 0);
  197. zero!(i32, 0);
  198. zero!(u32, 0);
  199. zero!(u16, 0);
  200. one!(f64, 1.0);
  201. one!(f32, 1.0);
  202. one!(u128, 1);
  203. one!(i128, 1);
  204. one!(u64, 1);
  205. one!(i64, 1);
  206. one!(i32, 1);
  207. one!(u32, 1);
  208. one!(u16, 1);
  209. impl<T> Incremental<T> for Window<T>
  210. where T: Default + AddAssign<T> + SubAssign<T> + From<u32> + Div<Output = T> +
  211. Mul<Output = T> + Sub<Output = T> + Copy
  212. {
  213. #[inline]
  214. fn refresh(&mut self, t: Instant) -> &Self {
  215. if !self.items.is_empty() {
  216. let (n_remove, sum, ps, count) =
  217. self.items.iter()
  218. .take_while(|x| t - x.time > self.size)
  219. .fold((0, self.sum, self.ps, self.count), |(n_remove, sum, ps, count), x| {
  220. (n_remove + 1, sum - x.value, ps - x.value * x.value, count - 1)
  221. });
  222. self.sum = sum;
  223. self.ps = ps;
  224. self.count = count;
  225. for _ in 0..n_remove {
  226. self.items.pop_front();
  227. }
  228. }
  229. if self.count > 0 {
  230. self.mean = self.sum / self.count.into();
  231. self.psa = self.ps / self.count.into();
  232. let c: T = self.count.into();
  233. self.var = (self.psa * c - c * self.mean * self.mean) / c;
  234. }
  235. self
  236. }
  237. /// Creates `Point { time, value }` and pushes to `self.items`.
  238. ///
  239. #[inline]
  240. fn add(&mut self, time: Instant, value: T) {
  241. let p = Point::new(time, value);
  242. self.sum += p.value;
  243. self.ps += p.value * p.value;
  244. self.count += 1;
  245. self.items.push_back(p);
  246. }
  247. #[inline]
  248. fn update(&mut self, time: Instant, value: T) {
  249. self.add(time, value);
  250. self.refresh(time);
  251. }
  252. }
  253. impl Incremental<Duration> for DurationWindow {
  254. #[inline]
  255. fn refresh(&mut self, t: Instant) -> &Self {
  256. if !self.items.is_empty() {
  257. let (n_remove, sum, count) =
  258. self.items.iter()
  259. .take_while(|x| t - x.time > self.size)
  260. .fold((0, self.sum, self.count), |(n_remove, sum, count), x| {
  261. (n_remove + 1, sum - x.value, count - 1)
  262. });
  263. self.sum = sum;
  264. self.count = count;
  265. for _ in 0..n_remove {
  266. self.items.pop_front();
  267. }
  268. }
  269. if self.count > 0 {
  270. self.mean = self.sum / self.count.into();
  271. }
  272. self
  273. }
  274. #[inline]
  275. fn add(&mut self, time: Instant, value: Duration) {
  276. let p = Point::new(time, value);
  277. self.sum += p.value;
  278. self.count += 1;
  279. self.items.push_back(p);
  280. }
  281. }
  282. impl<T> Window<T>
  283. where T: Default + Copy
  284. {
  285. pub fn mean(&self) -> T { self.mean }
  286. pub fn var(&self) -> T { self.var }
  287. pub fn psa(&self) -> T { self.psa }
  288. pub fn ps(&self) -> T { self.ps }
  289. pub fn count(&self) -> u32 { self.count }
  290. pub fn len(&self) -> usize { self.items.len() }
  291. pub fn is_empty(&self) -> bool { self.items.is_empty() }
  292. /// Returns the `Duration` between `t` and the first `Point` in `self.items`.
  293. ///
  294. /// If there are no items, returns `Duration { secs: 0, nanos: 0 }`.
  295. ///
  296. /// # Panics
  297. ///
  298. /// This function will panic if `t` is earlier than the first `Point`'s `Instant`.
  299. ///
  300. #[inline]
  301. pub fn elapsed(&self, t: Instant) -> Duration {
  302. self.items.front()
  303. .map(|p| {
  304. t - p.time
  305. }).unwrap_or_else(|| Duration::new(0, 0))
  306. }
  307. }
  308. impl<T> Window<T>
  309. where T: Float + Default
  310. {
  311. #[inline]
  312. pub fn std(&self) -> T { self.var.sqrt() }
  313. }
  314. impl DurationWindow {
  315. pub fn new(size: Duration) -> Self { DurationWindow { size, ..Default::default() } }
  316. pub fn mean(&self) -> Duration { self.mean }
  317. pub fn count(&self) -> u32 { self.count }
  318. pub fn len(&self) -> usize { self.items.len() }
  319. pub fn is_empty(&self) -> bool { self.items.is_empty() }
  320. #[inline]
  321. pub fn nanos(d: Duration) -> u64 { d.as_secs() * 1_000_000_000 + (d.subsec_nanos() as u64) }
  322. #[inline]
  323. pub fn micros(d: Duration) -> Option<u32> {
  324. if d <= Duration::new(4_294, 967_295_000) {
  325. Some((d.as_secs() * 1_000_000) as u32 + d.subsec_nanos() / 1_000u32)
  326. } else {
  327. None
  328. }
  329. }
  330. #[inline]
  331. pub fn mean_nanos(&self) -> u64 { DurationWindow::nanos(self.mean()) }
  332. #[inline]
  333. pub fn max(&self) -> Option<Duration> {
  334. self.items.iter()
  335. .map(|p| p.value)
  336. .max()
  337. }
  338. #[inline]
  339. pub fn max_nanos(&self) -> Option<u64> {
  340. self.max()
  341. .map(|x| DurationWindow::nanos(x))
  342. }
  343. #[inline]
  344. pub fn first(&self) -> Option<Duration> {
  345. self.items
  346. .front()
  347. .map(|pt| pt.value())
  348. }
  349. /// Returns the `Duration` between `t` and the first `Point` in `self.items`.
  350. ///
  351. /// If there are no items, returns `Duration { secs: 0, nanos: 0 }`.
  352. ///
  353. /// # Panics
  354. ///
  355. /// This function will panic if `t` is earlier than the first `Point`'s `Instant`.
  356. ///
  357. #[inline]
  358. pub fn elapsed(&self, t: Instant) -> Duration {
  359. self.items.front()
  360. .map(|p| {
  361. t - p.time
  362. }).unwrap_or_else(|| Duration::new(0, 0))
  363. }
  364. }
  365. }
  366. #[derive(Debug)]
  367. pub enum Latency {
  368. Ws(Exchange, Ticker, Duration),
  369. Http(Exchange, Duration),
  370. Trade(Exchange, Ticker, Duration),
  371. Terminate
  372. }
  373. #[derive(Debug)]
  374. pub enum ExperiencedLatency {
  375. GdaxWebsocket(Duration),
  376. GdaxHttpPublic(Duration),
  377. GdaxHttpPrivate(Duration),
  378. PlnxHttpPublic(Duration),
  379. PlnxHttpPrivate(Duration),
  380. PlnxOrderBook(Duration),
  381. KrknHttpPublic(Duration),
  382. KrknHttpPrivate(Duration),
  383. KrknTrade(Duration, &'static str, Option<Ticker>, Option<Side>),
  384. PlnxWs(Ticker),
  385. Terminate
  386. }
  387. #[derive(Debug, Clone)]
  388. pub struct Update {
  389. pub gdax_ws: Nanos,
  390. pub gdax_trade: Nanos,
  391. pub gdax_last: DateTime<Utc>
  392. }
  393. impl Default for Update {
  394. fn default() -> Self {
  395. Update {
  396. gdax_ws: 0,
  397. gdax_trade: 0,
  398. gdax_last: Utc::now(),
  399. }
  400. }
  401. }
  402. #[derive(Debug, Clone)]
  403. pub struct LatencyUpdate {
  404. pub gdax_ws: Nanos,
  405. pub krkn_pub: Nanos,
  406. pub krkn_priv: Nanos,
  407. pub plnx_pub: Nanos,
  408. pub plnx_priv: Nanos,
  409. pub plnx_order: Nanos,
  410. pub krkn_trade_30_mean: Nanos,
  411. pub krkn_trade_30_max: Nanos,
  412. pub krkn_trade_300_mean: Nanos,
  413. pub krkn_trade_300_max: Nanos,
  414. pub plnx_last: DateTime<Utc>,
  415. pub krkn_last: DateTime<Utc>,
  416. pub plnx_ws_count: u64,
  417. }
  418. impl Default for LatencyUpdate {
  419. fn default() -> Self {
  420. LatencyUpdate {
  421. gdax_ws : 0,
  422. krkn_pub : 0,
  423. krkn_priv : 0,
  424. plnx_pub : 0,
  425. plnx_priv : 0,
  426. plnx_order : 0,
  427. krkn_trade_30_mean : 0,
  428. krkn_trade_30_max : 0,
  429. krkn_trade_300_mean : 0,
  430. krkn_trade_300_max : 0,
  431. plnx_ws_count : 0,
  432. plnx_last : Utc::now(),
  433. krkn_last : Utc::now(),
  434. }
  435. }
  436. }
  437. pub struct Manager {
  438. pub tx: Sender<Latency>,
  439. pub channel: PubSub<Update>,
  440. thread: Option<JoinHandle<()>>,
  441. }
  442. pub struct LatencyManager {
  443. pub tx: Sender<ExperiencedLatency>,
  444. pub channel: PubSub<LatencyUpdate>,
  445. thread: Option<JoinHandle<()>>,
  446. }
  447. /// returns a DateTime equal to now - `dur`
  448. ///
  449. pub fn dt_from_dur(dur: Duration) -> DateTime<Utc> {
  450. let old_dur = chrono::Duration::nanoseconds(nanos(dur) as i64);
  451. Utc::now() - old_dur
  452. }
  453. struct Last {
  454. broadcast: Instant,
  455. plnx: Instant,
  456. krkn: Instant,
  457. gdax: Instant,
  458. }
  459. impl Default for Last {
  460. fn default() -> Self {
  461. Last {
  462. broadcast: Instant::now(),
  463. plnx: Instant::now(),
  464. krkn: Instant::now(),
  465. gdax: Instant::now(),
  466. }
  467. }
  468. }
  469. impl Manager {
  470. pub fn new(window: Duration,
  471. log_path: &'static str,
  472. measurements: Sender<OwnedMeasurement>) -> Self {
  473. let (tx, rx) = channel();
  474. let channel = PubSub::new();
  475. let channel_copy = channel.clone();
  476. let logger = file_logger(log_path, Severity::Info);
  477. info!(logger, "initializing");
  478. let mut gdax_ws = windows::DurationWindow::new(window);
  479. let mut gdax_trade = windows::DurationWindow::new(window);
  480. let mut last = Last::default();
  481. info!(logger, "entering loop");
  482. let thread = Some(thread::spawn(move || {
  483. loop {
  484. let loop_time = Instant::now();
  485. if let Ok(msg) = rx.recv_timeout(Duration::from_millis(1)) {
  486. debug!(logger, "rcvd {:?}", msg);
  487. match msg {
  488. Latency::Ws(_, _, dur) => {
  489. gdax_ws.update(loop_time, dur);
  490. last.gdax = loop_time;
  491. }
  492. Latency::Trade(_, ticker, dur) => {
  493. gdax_trade.update(loop_time, dur);
  494. last.gdax = loop_time;
  495. let nanos = windows::DurationWindow::nanos(dur);
  496. measurements.send(
  497. OwnedMeasurement::new("gdax_trade_api")
  498. .add_tag("ticker", ticker.as_str())
  499. .add_field("nanos", OwnedValue::Integer(nanos as i64))
  500. .set_timestamp(influx::now())).unwrap();
  501. }
  502. Latency::Terminate => break,
  503. _ => {}
  504. }
  505. }
  506. if loop_time - last.broadcast > Duration::from_millis(100) {
  507. debug!(logger, "initalizing broadcast");
  508. let update = Update {
  509. gdax_ws: gdax_ws.refresh(loop_time).mean_nanos(),
  510. gdax_trade: gdax_trade.refresh(loop_time).mean_nanos(),
  511. gdax_last: dt_from_dur(loop_time - last.gdax)
  512. };
  513. channel.send(update).unwrap();
  514. last.broadcast = loop_time;
  515. debug!(logger, "sent broadcast");
  516. }
  517. }
  518. debug!(logger, "latency manager terminating");
  519. }));
  520. Manager {
  521. tx,
  522. channel: channel_copy,
  523. thread,
  524. }
  525. }
  526. }
  527. impl Drop for LatencyManager {
  528. fn drop(&mut self) {
  529. for _ in 0..100 { self.tx.send(ExperiencedLatency::Terminate).unwrap(); }
  530. if let Some(thread) = self.thread.take() {
  531. let _ = thread.join();
  532. }
  533. }
  534. }
  535. impl Drop for Manager {
  536. fn drop(&mut self) {
  537. for _ in 0..100 { self.tx.send(Latency::Terminate).unwrap(); }
  538. if let Some(thread) = self.thread.take() {
  539. let _ = thread.join();
  540. }
  541. }
  542. }
  543. impl LatencyManager {
  544. pub fn new(d: Duration) -> Self {
  545. let (tx, rx) = channel();
  546. let tx_copy = tx.clone();
  547. let channel = PubSub::new();
  548. let channel_copy = channel.clone();
  549. //let w = w.clone();
  550. let thread = Some(thread::spawn(move || {
  551. let logger = file_logger("var/log/latency-manager.log", Severity::Info);
  552. info!(logger, "initializing DurationWindows");
  553. let mut gdax_ws = windows::DurationWindow::new(d);
  554. let mut gdax_priv = windows::DurationWindow::new(d);
  555. let mut krkn_pub = windows::DurationWindow::new(d);
  556. let mut krkn_priv = windows::DurationWindow::new(d);
  557. let mut plnx_pub = windows::DurationWindow::new(d);
  558. let mut plnx_priv = windows::DurationWindow::new(d);
  559. let mut plnx_order = windows::DurationWindow::new(d);
  560. let mut plnx_ws_count: windows::Window<u32> = windows::Window::new(d);
  561. // yes I am intentionally breaking from the hard-typed duration
  562. // window ... that was a stupid idea
  563. //
  564. let mut krkn_trade_30 = windows::DurationWindow::new(Duration::from_secs(30));
  565. let mut krkn_trade_300 = windows::DurationWindow::new(Duration::from_secs(300));
  566. let mut last = Last::default();
  567. thread::sleep(Duration::from_millis(1));
  568. info!(logger, "entering loop");
  569. loop {
  570. let loop_time = Instant::now();
  571. if let Ok(msg) = rx.recv() {
  572. debug!(logger, "new msg: {:?}", msg);
  573. match msg {
  574. ExperiencedLatency::Terminate => {
  575. crit!(logger, "terminating");
  576. break;
  577. }
  578. ExperiencedLatency::GdaxWebsocket(d) => gdax_ws.update(loop_time, d),
  579. ExperiencedLatency::GdaxHttpPrivate(d) => gdax_priv.update(loop_time, d),
  580. ExperiencedLatency::KrknHttpPublic(d) => {
  581. last.krkn = loop_time;
  582. krkn_pub.update(loop_time, d)
  583. }
  584. ExperiencedLatency::KrknHttpPrivate(d) => {
  585. last.krkn = loop_time;
  586. krkn_priv.update(loop_time, d)
  587. }
  588. ExperiencedLatency::PlnxHttpPublic(d) => {
  589. last.plnx = loop_time;
  590. plnx_pub.update(loop_time, d)
  591. }
  592. ExperiencedLatency::PlnxHttpPrivate(d) => {
  593. last.plnx = loop_time;
  594. plnx_priv.update(loop_time, d)
  595. }
  596. ExperiencedLatency::PlnxOrderBook(d) => {
  597. last.plnx = loop_time;
  598. plnx_order.update(loop_time, d)
  599. }
  600. ExperiencedLatency::PlnxWs(_) => {
  601. last.plnx = loop_time;
  602. plnx_ws_count.update(loop_time, 1_u32);
  603. }
  604. ExperiencedLatency::KrknTrade(d, cmd, _, _) => {
  605. debug!(logger, "new KrknTrade";
  606. "cmd" => cmd);
  607. last.krkn = loop_time;
  608. krkn_trade_30.update(loop_time, d);
  609. krkn_trade_300.update(loop_time, d);
  610. }
  611. other => {
  612. warn!(logger, "unexpected msg: {:?}", other);
  613. }
  614. }
  615. }
  616. if loop_time - last.broadcast > Duration::from_millis(100) {
  617. debug!(logger, "initalizing broadcast");
  618. // note - because we mutated the Window instances
  619. // above, we need a fresh Instant to avoid less than other
  620. // panic
  621. //
  622. krkn_trade_30.refresh(loop_time);
  623. krkn_trade_300.refresh(loop_time);
  624. let update = LatencyUpdate {
  625. gdax_ws: gdax_ws.refresh(loop_time).mean_nanos(),
  626. krkn_pub: krkn_pub.refresh(loop_time).mean_nanos(),
  627. krkn_priv: krkn_priv.refresh(loop_time).mean_nanos(),
  628. plnx_pub: plnx_pub.refresh(loop_time).mean_nanos(),
  629. plnx_priv: plnx_priv.refresh(loop_time).mean_nanos(),
  630. plnx_order: plnx_order.refresh(loop_time).mean_nanos(),
  631. krkn_trade_30_mean: krkn_trade_30.mean_nanos(),
  632. krkn_trade_30_max: krkn_trade_30.max_nanos().unwrap_or(0),
  633. krkn_trade_300_mean: krkn_trade_300.mean_nanos(),
  634. krkn_trade_300_max: krkn_trade_300.max_nanos().unwrap_or(0),
  635. plnx_last: dt_from_dur(loop_time - last.plnx),
  636. krkn_last: dt_from_dur(loop_time - last.krkn),
  637. plnx_ws_count: plnx_ws_count.refresh(loop_time).count() as u64,
  638. };
  639. channel.send(update).unwrap();
  640. last.broadcast = loop_time;
  641. debug!(logger, "sent broadcast");
  642. }
  643. }
  644. crit!(logger, "goodbye");
  645. }));
  646. LatencyManager {
  647. tx: tx_copy,
  648. channel: channel_copy,
  649. thread
  650. }
  651. }
  652. }