本文展示了如何通过对生产中的请求进行采样并使用马尔科夫链生成假的请求,从而生成用于测试目的的真实用户流量。
下一个事件Xn+1Xn+1}Xn+1的概率由上一个已知事件P(Xn+1|Xn)P(Xn+1}|Xn})P(Xn+1|Xn)P(Xn+1|Xn)或前m个m个事件确定,这给了我们一个m,m,m阶的马尔可夫链:
P(Xn=xn|Xn−1=xn−1,.。。。,X n−m=x n−m),n>;m。P(X_{n}=x_{n}|X_{n-1}=x_{n-1},...,X_{n-m}=x_{n-m}),n\gt m.P(X n=x n|X n−1=x n−1,.。。。,X n−m=x n−m),n>;m。我们可以跟踪实际的用户行为,并通过计数事件并根据事件之前的事件计算每个事件的加权概率来建立模型。
例如,101010个用户完成了一篇博客帖子的撰写。9 9 9个用户后来将其发布,其中一个用户将其销毁。这为我们提供了P(P o s t P u b l i s h e d|P o s T F I n I s h e d)=0.9 P(发布后|完成后)=0.9 P(P o s t P u b l i s h e d|P o s t F I n i s h e d)=0的概率。9和P(P o s t T r a s h e d|P o s t F I n i s h e d)=0.1P(PostTrashed|PostFinish)=0.1 P(P o s t T r a s h e d|P o s t F I n i s h e d)=0。1.然后我们可以用轮盘赌选择一个随机事件。
当您需要一些类似真实用户的虚假流量时,这对于压力或烟雾测试很有用。
让声明马尔可夫链的结构。映射实例将保存任何先前系列事件Vec<;T&>的事件计数BTreeMap&t;T,usize&>;。
Pub struct MarkovChain<;T&>;其中T:克隆+订单,{顺序:usize,实例:BTreeMap<;Vec<;T>;,BTreeMap<;T,usize<;>;,内存:VEC<;T>;,RNG:ThreadRng,}。
要建立模型,我们必须仔细检查所有观察到的事件,并计算在所有n>;m个事件之后发生n个事件的次数。我们还跟踪内存向量中最后已知的m个m个事件,这些事件将用于生成下一个事件。
实施<;T>;MarkovChain<;T>;其中T:Clone+Ord,{pub FN update(&;mut Self,Events:&;[T]){let events:VEC<;_>;=events。To_vec();事件中的历史。Windows(Self.Order+1){//将窗口拆分为0..N-1和N让Previous=History[0.。Self.Order]。To_vec();let current=历史。最后一个()。克隆()。UNWRAP();//统计当前事件自身的.occurations的发生次数。条目(上一个)。或_default()。条目(当前)。AND_MODIFY(|COUNT|*COUNT+=1)。或_INSERT(1);}//更新内部存储器自身.memory。预留(自身订单);用于事件中的事件。INTO_ITER()。Rev()。Take(Self.order){Self.Memory.。INSERT(0,Event);}自身.memory。截断(自身.order);}//...。
GENERATE_FROM函数获取内存,查找事件并从中选择事件。我们使用兰德机箱提供的CHOOSE_WEIGNED函数。
Pub FN GENERATE_FROM(&;mut self,memory:&;[T])->;option<;T>;{assert_eq!(记忆。Len(),sel.order,";无效的内存大小";);如果让某些(发生次数)=自身发生次数。Get(Memory){//获取每个已知事件的发生次数。//我们需要`SliceRandom::Choose_Weight`的VEC。设ocurence_count:VEC<;_>;=发生次数。ITER()。Map(|(事件,计数)|(事件。克隆(),*计数))。Collect();//根据事件的计数occurence_counts选择一个随机事件。CHOOSE_WEATED(&;mut self.rng,|(_,count)|*count)。Map(|(event,_)|event)。OK()。克隆()}否则{//内存中没有匹配项}}。
生成新事件后,我们更新内部内存。然后将根据最新的内存计算下一个事件。
Pub FN GENERATE(&;mut self,update_memory:bool)->;Option<;T>;{let last_memory=sel.memory。克隆();如果让SOME(NEXT)=SELF。GENERATE_FROM(&;LAST_MEMORY){IF UPDATE_MEMORY{//更新内部存储器自身.memory。插入(0,下一步。Clone());self.memory。截断(self.order);}一些(下一个)}其他{无}}。
Pub struct MarkovChainIter<;,T>;Where T:Clone+Ord,{chain:&;mut MarkovChain<;T>;,}Imp<;,T>;Iterator for MarkovChainIter<;,T>;Where T:Clone+Ord,{type Item=T;FN Next(&;mut self)->;Option<;self::Item&>;{sel.chain.。Generate(True)}}Impl<;T>;MarkovChain<;T>;其中T:Clone+Ord,{pub FN ITER(&;mut Self)->;MarkovChainIter<;T>;{MarkovChainIter{Chain:Self}}//...}。
为了在实际操作中看到它,我们声明所有可能的操作的枚举。当然,在现实世界的用例中,这些操作可能要复杂得多。
让mut chain=MarkovChain::New(1);让Actions=vec![userAction::signin,userAction::ListTodos,userAction::CreateTodo,userAction::Signout,userAction::CreateTodo,userAction::Signin,userAction::ListTodos,userAction::DeleteTodo,userAction::DeleteTodo,userAction::CreateTodo,userAction::Sigout,userAction::Sign In,userAction::ListTodos,userAction::CreateTodo,userAction::DeleteTodo,userAction::ListTodos,userAction::DeleteTodo,userAction::signout,];chain。更新(&;操作);
连锁反应。ITER()。Take(16){if action==userAction::signin{println!(";##新会话##";);}println!(";{:?}";,action);}。
请注意,每次登录后都有一个ListTodos,这意味着P(L i s t T o d o s|S i g n i n)=1 P(ListTodos|signin)=1 P(L i s t T o d o s|S i g n n)=1。构建模型可以表示应用程序的固有规则。不会生成一系列操作,概率为0 0,这比统一生成的随机数据要好得多。
某些操作组合可能是不可能的,因为它们会违反业务规则。这些操作可以被过滤掉,或者留下来测试无效的请求。
我们可以用它做更多的事情,这只是一个简单的介绍,介绍了一个可以用来生成一些假请求的便捷工具。