Skip to content

Commit bad71fa

Browse files
committed
Add store ldshm subcmd
Changed from the old `store clone` to instead simply load any shm buffer matching a user provided `FQME: str` pattern; writing to parquet file is only done if an explicit option flag is passed by user. Implement new `iter_dfs_from_shms()` generator which allows interatively loading both 1m and 1s buffers delivering the `Path`, `ShmArray` and `polars.DataFrame` instances per matching file B) Also add a todo for a `NativeStorageClient.clear_range()` method.
1 parent 0c99749 commit bad71fa

2 files changed

Lines changed: 170 additions & 83 deletions

File tree

piker/storage/cli.py

Lines changed: 144 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,13 @@
2020
"""
2121
from __future__ import annotations
2222
from pathlib import Path
23+
import time
24+
from typing import Generator
2325
# from typing import TYPE_CHECKING
2426

2527
import polars as pl
2628
import numpy as np
29+
import tractor
2730
# import pendulum
2831
from rich.console import Console
2932
import trio
@@ -32,6 +35,16 @@
3235

3336
from piker.service import open_piker_runtime
3437
from piker.cli import cli
38+
from piker.config import get_conf_dir
39+
from piker.data import (
40+
maybe_open_shm_array,
41+
def_iohlcv_fields,
42+
ShmArray,
43+
)
44+
from piker.data.history import (
45+
_default_hist_size,
46+
_default_rt_size,
47+
)
3548
from . import (
3649
log,
3750
)
@@ -132,8 +145,6 @@ def anal(
132145

133146
) -> np.ndarray:
134147

135-
import tractor
136-
137148
async def main():
138149
async with (
139150
open_piker_runtime(
@@ -171,99 +182,150 @@ async def main():
171182
trio.run(main)
172183

173184

185+
def iter_dfs_from_shms(fqme: str) -> Generator[
186+
tuple[Path, ShmArray, pl.DataFrame],
187+
None,
188+
None,
189+
]:
190+
# shm buffer size table based on known sample rates
191+
sizes: dict[str, int] = {
192+
'hist': _default_hist_size,
193+
'rt': _default_rt_size,
194+
}
195+
196+
# load all detected shm buffer files which have the
197+
# passed FQME pattern in the file name.
198+
shmfiles: list[Path] = []
199+
shmdir = Path('/dev/shm/')
200+
201+
for shmfile in shmdir.glob(f'*{fqme}*'):
202+
filename: str = shmfile.name
203+
204+
# skip index files
205+
if (
206+
'_first' in filename
207+
or '_last' in filename
208+
):
209+
continue
210+
211+
assert shmfile.is_file()
212+
log.debug(f'Found matching shm buffer file: {filename}')
213+
shmfiles.append(shmfile)
214+
215+
for shmfile in shmfiles:
216+
217+
# lookup array buffer size based on file suffix
218+
# being either .rt or .hist
219+
size: int = sizes[shmfile.name.rsplit('.')[-1]]
220+
221+
# attach to any shm buffer, load array into polars df,
222+
# write to local parquet file.
223+
shm, opened = maybe_open_shm_array(
224+
key=shmfile.name,
225+
size=size,
226+
dtype=def_iohlcv_fields,
227+
readonly=True,
228+
)
229+
assert not opened
230+
ohlcv = shm.array
231+
232+
start = time.time()
233+
234+
# XXX: thanks to this SO answer for this conversion tip:
235+
# https://stackoverflow.com/a/72054819
236+
df = pl.DataFrame({
237+
field_name: ohlcv[field_name]
238+
for field_name in ohlcv.dtype.fields
239+
})
240+
delay: float = round(
241+
time.time() - start,
242+
ndigits=6,
243+
)
244+
log.info(
245+
f'numpy -> polars conversion took {delay} secs\n'
246+
f'polars df: {df}'
247+
)
248+
249+
yield (
250+
shmfile,
251+
shm,
252+
df,
253+
)
254+
255+
174256
@store.command()
175-
def clone(
257+
def ldshm(
176258
fqme: str,
259+
260+
write_parquet: bool = False,
261+
177262
) -> None:
178-
import time
179-
from piker.config import get_conf_dir
180-
from piker.data import (
181-
maybe_open_shm_array,
182-
def_iohlcv_fields,
183-
)
184-
import polars as pl
185-
186-
# TODO: actually look up an existing shm buf (set) from
187-
# an fqme and file name parsing..
188-
# open existing shm buffer for kucoin backend
189-
key: str = 'piker.brokerd[3595d316-3c15-46].xmrusdt.kucoin.hist'
190-
shmpath: Path = Path('/dev/shm') / key
191-
assert shmpath.is_file()
263+
'''
264+
Linux ONLY: load any fqme file name matching shm buffer from
265+
/dev/shm/ into an OHLCV numpy array and polars DataFrame,
266+
optionally write to .parquet file.
192267
268+
'''
193269
async def main():
194270
async with (
195271
open_piker_runtime(
196272
'polars_boi',
197273
enable_modules=['piker.data._sharedmem'],
198274
),
199275
):
200-
# attach to any shm buffer, load array into polars df,
201-
# write to local parquet file.
202-
shm, opened = maybe_open_shm_array(
203-
key=key,
204-
dtype=def_iohlcv_fields,
205-
)
206-
assert not opened
207-
ohlcv = shm.array
208-
209-
start = time.time()
210-
211-
# XXX: thanks to this SO answer for this conversion tip:
212-
# https://stackoverflow.com/a/72054819
213-
df = pl.DataFrame({
214-
field_name: ohlcv[field_name]
215-
for field_name in ohlcv.dtype.fields
216-
})
217-
delay: float = round(
218-
time.time() - start,
219-
ndigits=6,
220-
)
221-
print(
222-
f'numpy -> polars conversion took {delay} secs\n'
223-
f'polars df: {df}'
224-
)
225276

226-
# compute ohlc properties for naming
227-
times: np.ndarray = ohlcv['time']
228-
secs: float = times[-1] - times[-2]
229-
if secs < 1.:
230-
breakpoint()
231-
raise ValueError(
232-
f'Something is wrong with time period for {shm}:\n{ohlcv}'
233-
)
234-
235-
timeframe: str = f'{secs}s'
236-
237-
# write to parquet file
238-
datadir: Path = get_conf_dir() / 'parqdb'
239-
if not datadir.is_dir():
240-
datadir.mkdir()
241-
242-
path: Path = datadir / f'{fqme}.{timeframe}.parquet'
243-
244-
# write to fs
245-
start = time.time()
246-
df.write_parquet(path)
247-
delay: float = round(
248-
time.time() - start,
249-
ndigits=6,
250-
)
251-
print(
252-
f'parquet write took {delay} secs\n'
253-
f'file path: {path}'
254-
)
277+
df: pl.DataFrame | None = None
278+
for shmfile, shm, df in iter_dfs_from_shms(fqme):
255279

256-
# read back from fs
257-
start = time.time()
258-
read_df: pl.DataFrame = pl.read_parquet(path)
259-
delay: float = round(
260-
time.time() - start,
261-
ndigits=6,
262-
)
263-
print(
264-
f'parquet read took {delay} secs\n'
265-
f'polars df: {read_df}'
266-
)
280+
# compute ohlc properties for naming
281+
times: np.ndarray = shm.array['time']
282+
secs: float = times[-1] - times[-2]
283+
if secs < 1.:
284+
breakpoint()
285+
raise ValueError(
286+
f'Something is wrong with time period for {shm}:\n{times}'
287+
)
288+
289+
# TODO: maybe only optionally enter this depending
290+
# on some CLI flags and/or gap detection?
291+
await tractor.breakpoint()
292+
293+
# write to parquet file?
294+
if write_parquet:
295+
timeframe: str = f'{secs}s'
296+
297+
datadir: Path = get_conf_dir() / 'nativedb'
298+
if not datadir.is_dir():
299+
datadir.mkdir()
300+
301+
path: Path = datadir / f'{fqme}.{timeframe}.parquet'
302+
303+
# write to fs
304+
start = time.time()
305+
df.write_parquet(path)
306+
delay: float = round(
307+
time.time() - start,
308+
ndigits=6,
309+
)
310+
log.info(
311+
f'parquet write took {delay} secs\n'
312+
f'file path: {path}'
313+
)
314+
315+
# read back from fs
316+
start = time.time()
317+
read_df: pl.DataFrame = pl.read_parquet(path)
318+
delay: float = round(
319+
time.time() - start,
320+
ndigits=6,
321+
)
322+
print(
323+
f'parquet read took {delay} secs\n'
324+
f'polars df: {read_df}'
325+
)
326+
327+
if df is None:
328+
log.error(f'No matching shm buffers for {fqme} ?')
267329

268330
trio.run(main)
269331

piker/storage/nativedb.py

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,14 @@ def mk_ohlcv_shm_keyed_filepath(
137137
return path
138138

139139

140+
def unpack_fqme_from_parquet_filepath(path: Path) -> str:
141+
142+
filename: str = str(path.name)
143+
fqme, fmt_descr, suffix = filename.split('.')
144+
assert suffix == 'parquet'
145+
return fqme
146+
147+
140148
ohlc_key_map = None
141149

142150

@@ -347,10 +355,27 @@ async def delete_ts(
347355
path.unlink()
348356
log.warning(f'Deleting parquet entry:\n{path}')
349357
else:
350-
log.warning(f'No path exists:\n{path}')
358+
log.error(f'No path exists:\n{path}')
351359

352360
return path
353361

362+
# TODO: allow wiping and refetching a segment of the OHLCV timeseries
363+
# data.
364+
# def clear_range(
365+
# self,
366+
# key: str,
367+
# start_dt: datetime,
368+
# end_dt: datetime,
369+
# timeframe: int | None = None,
370+
# ) -> pl.DataFrame:
371+
# '''
372+
# Clear and re-fetch a range of datums for the OHLCV time series.
373+
374+
# Useful for series editing from a chart B)
375+
376+
# '''
377+
# ...
378+
354379

355380
@acm
356381
async def get_client(

0 commit comments

Comments
 (0)