Skip to content

Commit e8c14b1

Browse files
committed
Merge remote-tracking branch 'origin/main' into bruceg/OPA-4723-add-templated-json-generator
2 parents ab0aba7 + 669ba5d commit e8c14b1

5 files changed

Lines changed: 83 additions & 98 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
3737
- Added new payload generator `templated_json` that uses a user-specificied
3838
payload template to produce JSON lines. See
3939
`lading_payload/README.templated_json.md` for more details.
40+
- `payloadtool` now supports a `--dump=FILENAME` option which saves the generated data
41+
blocks to the named file.
4042
## Fixed
4143
- Fixed a race condition in `lading_signal` that caused lading to hang on shutdown.
4244
- Fixed a tag parsing bug that resulted in tags with hyphenated keys being merged with proceeding tag values.

lading/src/bin/payloadtool.rs

Lines changed: 74 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ mod alloc_tracker {
139139

140140
use std::fmt;
141141
use std::fs::{File, OpenOptions};
142-
use std::io::Read;
142+
use std::io::{Read, Write};
143143
use std::num::NonZeroU32;
144144
use std::path::{Path, PathBuf};
145145
use std::time::Instant;
@@ -150,7 +150,7 @@ use tokio::runtime::Builder;
150150
use anyhow::{Context, Result, anyhow};
151151
use byte_unit::{Byte, Unit, UnitType};
152152
use clap::Parser;
153-
use lading::generator::{self, http::Method};
153+
use lading::generator::{self, file_gen, http::Method};
154154
use lading_payload::block;
155155
use rand::{SeedableRng, rngs::StdRng};
156156
use sha2::{Digest, Sha256};
@@ -276,14 +276,17 @@ struct Args {
276276
/// Report memory allocation statistics at completion
277277
#[clap(short = 'm', long)]
278278
memory_stats: bool,
279+
/// Dump generated payload data to this file
280+
#[clap(long)]
281+
dump: Option<PathBuf>,
279282
}
280283

281284
fn generate_and_check(
282285
config: &lading_payload::Config,
283286
seed: [u8; 32],
284287
total_bytes: NonZeroU32,
285288
max_block_size: Byte,
286-
compute_fingerprint: bool,
289+
args: &Args,
287290
) -> Result<Option<Fingerprint>> {
288291
let mut rng = StdRng::from_seed(seed);
289292
let start = Instant::now();
@@ -306,8 +309,23 @@ fn generate_and_check(
306309
info!("Payload generation took {:?}", start.elapsed());
307310
trace!("Payload: {:#?}", blocks);
308311

312+
if let Some(dump_path) = args.dump.as_deref() {
313+
let mut dump_file = OpenOptions::new()
314+
.write(true)
315+
.create(true)
316+
.truncate(true)
317+
.open(dump_path)
318+
.with_context(|| format!("Could not open dump file at: {}", dump_path.display()))?;
319+
for block in &blocks {
320+
dump_file.write_all(&block.bytes).with_context(|| {
321+
format!("Failed to write to dump file: {}", dump_path.display())
322+
})?;
323+
}
324+
info!("Dumped payload to {}", dump_path.display());
325+
}
326+
309327
// Compute fingerprint if requested: SHA256 hash and Shannon entropy.
310-
let fingerprint = if compute_fingerprint {
328+
let fingerprint = args.fingerprint.then(|| {
311329
let mut hasher = Sha256::new();
312330
let mut all_bytes = Vec::new();
313331
for block in &blocks {
@@ -317,10 +335,8 @@ fn generate_and_check(
317335
let result = hasher.finalize();
318336
let hash = format!("{result:x}");
319337
let entropy = shannon_entropy(&all_bytes);
320-
Some(Fingerprint { hash, entropy })
321-
} else {
322-
None
323-
};
338+
Fingerprint { hash, entropy }
339+
});
324340

325341
let mut total_generated_bytes: u32 = 0;
326342
for block in &blocks {
@@ -348,55 +364,54 @@ fn generate_and_check(
348364
}
349365

350366
#[expect(clippy::too_many_lines)]
351-
fn check_generator(
352-
config: &generator::Config,
353-
compute_fingerprint: bool,
354-
) -> Result<Option<Fingerprint>> {
367+
fn check_generator(config: &generator::Config, args: &Args) -> Result<Option<Fingerprint>> {
355368
match &config.inner {
356-
generator::Inner::FileGen(_) => {
357-
if compute_fingerprint {
358-
warn!("FileGen not supported for fingerprinting");
359-
return Ok(None);
360-
}
361-
unimplemented!("FileGen not supported")
369+
generator::Inner::FileGen(g) => {
370+
let (cache_size, variant, seed, max_block_size) = match g {
371+
file_gen::Config::Traditional(c) => (
372+
c.maximum_prebuild_cache_size_bytes,
373+
&c.variant,
374+
c.seed,
375+
c.maximum_block_size,
376+
),
377+
file_gen::Config::Logrotate(c) => (
378+
c.maximum_prebuild_cache_size_bytes,
379+
&c.variant,
380+
c.seed,
381+
c.maximum_block_size,
382+
),
383+
#[cfg(feature = "logrotate_fs")]
384+
file_gen::Config::LogrotateFs(c) => (
385+
c.maximum_prebuild_cache_size_bytes,
386+
&c.variant,
387+
c.seed,
388+
c.maximum_block_size,
389+
),
390+
};
391+
#[expect(clippy::cast_possible_truncation)]
392+
let total_bytes = NonZeroU32::new(cache_size.as_u128() as u32)
393+
.expect("Non-zero max prebuild cache size");
394+
generate_and_check(variant, seed, total_bytes, max_block_size, args)
362395
}
363396
generator::Inner::UnixDatagram(g) => {
364397
let max_block_size = UDP_PACKET_LIMIT_BYTES;
365398
#[expect(clippy::cast_possible_truncation)]
366399
let total_bytes = NonZeroU32::new(g.maximum_prebuild_cache_size_bytes.as_u128() as u32)
367400
.expect("Non-zero max prebuild cache size");
368-
generate_and_check(
369-
&g.variant,
370-
g.seed,
371-
total_bytes,
372-
max_block_size,
373-
compute_fingerprint,
374-
)
401+
generate_and_check(&g.variant, g.seed, total_bytes, max_block_size, args)
375402
}
376403
generator::Inner::Tcp(g) => {
377404
#[expect(clippy::cast_possible_truncation)]
378405
let total_bytes = NonZeroU32::new(g.maximum_prebuild_cache_size_bytes.as_u128() as u32)
379406
.expect("Non-zero max prebuild cache size");
380-
generate_and_check(
381-
&g.variant,
382-
g.seed,
383-
total_bytes,
384-
g.maximum_block_size,
385-
compute_fingerprint,
386-
)
407+
generate_and_check(&g.variant, g.seed, total_bytes, g.maximum_block_size, args)
387408
}
388409
generator::Inner::Udp(g) => {
389410
#[expect(clippy::cast_possible_truncation)]
390411
let total_bytes = NonZeroU32::new(g.maximum_prebuild_cache_size_bytes.as_u128() as u32)
391412
.expect("Non-zero max prebuild cache size");
392413
let max_block_size = UDP_PACKET_LIMIT_BYTES;
393-
generate_and_check(
394-
&g.variant,
395-
g.seed,
396-
total_bytes,
397-
max_block_size,
398-
compute_fingerprint,
399-
)
414+
generate_and_check(&g.variant, g.seed, total_bytes, max_block_size, args)
400415
}
401416
generator::Inner::Http(g) => {
402417
let (variant, max_prebuild_cache_size_bytes) = match &g.method {
@@ -409,23 +424,17 @@ fn check_generator(
409424
#[expect(clippy::cast_possible_truncation)]
410425
let total_bytes = NonZeroU32::new(max_prebuild_cache_size_bytes.as_u128() as u32)
411426
.expect("Non-zero max prebuild cache size");
412-
generate_and_check(
413-
variant,
414-
g.seed,
415-
total_bytes,
416-
g.maximum_block_size,
417-
compute_fingerprint,
418-
)
427+
generate_and_check(variant, g.seed, total_bytes, g.maximum_block_size, args)
419428
}
420429
generator::Inner::SplunkHec(_) => {
421-
if compute_fingerprint {
430+
if args.fingerprint {
422431
warn!("SplunkHec not supported for fingerprinting");
423432
return Ok(None);
424433
}
425434
unimplemented!("SplunkHec not supported")
426435
}
427436
generator::Inner::FileTree(_) => {
428-
if compute_fingerprint {
437+
if args.fingerprint {
429438
warn!("FileTree not supported for fingerprinting");
430439
return Ok(None);
431440
}
@@ -435,61 +444,43 @@ fn check_generator(
435444
#[expect(clippy::cast_possible_truncation)]
436445
let total_bytes = NonZeroU32::new(g.maximum_prebuild_cache_size_bytes.as_u128() as u32)
437446
.expect("Non-zero max prebuild cache size");
438-
generate_and_check(
439-
&g.variant,
440-
g.seed,
441-
total_bytes,
442-
g.maximum_block_size,
443-
compute_fingerprint,
444-
)
447+
generate_and_check(&g.variant, g.seed, total_bytes, g.maximum_block_size, args)
445448
}
446449
generator::Inner::UnixStream(g) => {
447450
#[expect(clippy::cast_possible_truncation)]
448451
let total_bytes = NonZeroU32::new(g.maximum_prebuild_cache_size_bytes.as_u128() as u32)
449452
.expect("Non-zero max prebuild cache size");
450-
generate_and_check(
451-
&g.variant,
452-
g.seed,
453-
total_bytes,
454-
g.maximum_block_size,
455-
compute_fingerprint,
456-
)
453+
generate_and_check(&g.variant, g.seed, total_bytes, g.maximum_block_size, args)
457454
}
458455
generator::Inner::PassthruFile(g) => {
459456
#[expect(clippy::cast_possible_truncation)]
460457
let total_bytes = NonZeroU32::new(g.maximum_prebuild_cache_size_bytes.as_u128() as u32)
461458
.expect("Non-zero max prebuild cache size");
462-
generate_and_check(
463-
&g.variant,
464-
g.seed,
465-
total_bytes,
466-
g.maximum_block_size,
467-
compute_fingerprint,
468-
)
459+
generate_and_check(&g.variant, g.seed, total_bytes, g.maximum_block_size, args)
469460
}
470461
generator::Inner::ProcessTree(_) => {
471-
if compute_fingerprint {
462+
if args.fingerprint {
472463
warn!("ProcessTree not supported for fingerprinting");
473464
return Ok(None);
474465
}
475466
unimplemented!("ProcessTree not supported")
476467
}
477468
generator::Inner::ProcFs(_) => {
478-
if compute_fingerprint {
469+
if args.fingerprint {
479470
warn!("ProcFs not supported for fingerprinting");
480471
return Ok(None);
481472
}
482473
unimplemented!("ProcFs not supported")
483474
}
484475
generator::Inner::Container(_) => {
485-
if compute_fingerprint {
476+
if args.fingerprint {
486477
warn!("Container not supported for fingerprinting");
487478
return Ok(None);
488479
}
489480
unimplemented!("Container not supported")
490481
}
491482
generator::Inner::Kubernetes(_) => {
492-
if compute_fingerprint {
483+
if args.fingerprint {
493484
warn!("Kubernetes not supported for fingerprinting");
494485
return Ok(None);
495486
}
@@ -500,13 +491,7 @@ fn check_generator(
500491
generator::trace_agent::validate_cache_size(g.maximum_prebuild_cache_size_bytes)
501492
.map_err(|e| anyhow::anyhow!("Cache size validation failed: {e}"))?;
502493
let conf = lading_payload::Config::TraceAgent(g.variant);
503-
generate_and_check(
504-
&conf,
505-
g.seed,
506-
total_bytes,
507-
g.maximum_block_size,
508-
compute_fingerprint,
509-
)
494+
generate_and_check(&conf, g.seed, total_bytes, g.maximum_block_size, args)
510495
}
511496
}
512497
}
@@ -548,24 +533,24 @@ async fn inner_main() -> Result<()> {
548533
config.generator.len()
549534
);
550535

551-
if let Some(generator_id) = args.generator_id {
536+
if let Some(ref generator_id) = args.generator_id {
552537
let generator = config
553538
.generator
554539
.iter()
555540
.find(|g| {
556541
let Some(ref id) = g.general.id else {
557542
return false;
558543
};
559-
*id == generator_id
544+
id == generator_id
560545
})
561546
.ok_or_else(|| anyhow!("No generator found with id: {generator_id}"))?;
562-
let fingerprint = check_generator(generator, args.fingerprint)?;
547+
let fingerprint = check_generator(generator, &args)?;
563548
if args.fingerprint
564549
&& let Some(fp) = fingerprint
565550
{
566-
if let Some(verify_path) = args.verify {
551+
if let Some(ref verify_path) = args.verify {
567552
let expected_content =
568-
fs::read_to_string(&verify_path).await.with_context(|| {
553+
fs::read_to_string(verify_path).await.with_context(|| {
569554
format!("Could not read verify file {}", verify_path.display())
570555
})?;
571556

@@ -597,19 +582,17 @@ async fn inner_main() -> Result<()> {
597582
} else {
598583
let mut all_fingerprints = Vec::new();
599584
for generator in config.generator {
600-
let fingerprint = check_generator(&generator, args.fingerprint)?;
601-
if args.fingerprint
602-
&& let Some(fp) = fingerprint
603-
{
585+
let fingerprint = check_generator(&generator, &args)?;
586+
if let Some(fp) = fingerprint {
604587
let gen_id = generator.general.id.as_deref().unwrap_or("<unnamed>");
605588
all_fingerprints.push((gen_id.to_string(), fp));
606589
}
607590
}
608591
if args.fingerprint && !all_fingerprints.is_empty() {
609-
if let Some(verify_path) = args.verify {
592+
if let Some(ref verify_path) = args.verify {
610593
// Read expected fingerprints from file
611594
let expected_content =
612-
fs::read_to_string(&verify_path).await.with_context(|| {
595+
fs::read_to_string(verify_path).await.with_context(|| {
613596
format!("Could not read verify file {}", verify_path.display())
614597
})?;
615598

lading/src/generator/file_gen/logrotate.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -140,10 +140,10 @@ pub struct Config {
140140
bytes_per_second: Option<Byte>,
141141
/// Defines the maximum internal cache of this log target. `file_gen` will
142142
/// pre-build its outputs up to the byte capacity specified here.
143-
maximum_prebuild_cache_size_bytes: Byte,
143+
pub maximum_prebuild_cache_size_bytes: Byte,
144144
/// The maximum size in bytes of the largest block in the prebuild cache.
145145
#[serde(default = "lading_payload::block::default_maximum_block_size")]
146-
maximum_block_size: byte_unit::Byte,
146+
pub maximum_block_size: byte_unit::Byte,
147147
/// Whether to use a fixed or streaming block cache
148148
#[serde(default = "lading_payload::block::default_cache_method")]
149149
block_cache_method: block::CacheMethod,

lading/src/generator/file_gen/logrotate_fs.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,13 +51,13 @@ pub struct Config {
5151
/// files will be present in the root path.
5252
max_depth: u8,
5353
/// Sets the [`crate::payload::Config`] of this template.
54-
variant: lading_payload::Config,
54+
pub variant: lading_payload::Config,
5555
/// Defines the maximum internal cache of this log target. `file_gen` will
5656
/// pre-build its outputs up to the byte capacity specified here.
57-
maximum_prebuild_cache_size_bytes: byte_unit::Byte,
57+
pub maximum_prebuild_cache_size_bytes: byte_unit::Byte,
5858
/// The maximum size in bytes of the largest block in the prebuild cache.
5959
#[serde(default = "lading_payload::block::default_maximum_block_size")]
60-
maximum_block_size: byte_unit::Byte,
60+
pub maximum_block_size: byte_unit::Byte,
6161
/// The mount-point for this filesystem
6262
mount_point: PathBuf,
6363
/// The load profile, controlling bytes or blocks per second as a function of time.

lading/src/generator/file_gen/traditional.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -107,10 +107,10 @@ pub struct Config {
107107
bytes_per_second: Option<Byte>,
108108
/// Defines the maximum internal cache of this log target. `file_gen` will
109109
/// pre-build its outputs up to the byte capacity specified here.
110-
maximum_prebuild_cache_size_bytes: Byte,
110+
pub maximum_prebuild_cache_size_bytes: Byte,
111111
/// The maximum size in bytes of the largest block in the prebuild cache.
112112
#[serde(default = "lading_payload::block::default_maximum_block_size")]
113-
maximum_block_size: byte_unit::Byte,
113+
pub maximum_block_size: byte_unit::Byte,
114114
/// Whether to use a fixed or streaming block cache
115115
#[serde(default = "lading_payload::block::default_cache_method")]
116116
block_cache_method: block::CacheMethod,

0 commit comments

Comments
 (0)