Legend:
Page
Library
Module
Module type
Parameter
Class
Class type
Source
Source file dependencies_matrix_code.ml
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427(* Yoann Padioleau
*
* Copyright (C) 2012 Facebook
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* version 2.1 as published by the Free Software Foundation, with the
* special exception on linking described in file license.txt.
*
* This library is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the file
* license.txt for more details.
*)openCommonmoduleG=Graph_codemoduleG2=Graph_code_opti(*****************************************************************************)(* Prelude *)(*****************************************************************************)(*
* See http://en.wikipedia.org/wiki/Design_structure_matrix
*)(*****************************************************************************)(* Types *)(*****************************************************************************)(* Dependency Structure Matrix.
*
* If A depends on B, e.g. visual/ depends on commons/,
* then we will increment 'row of visual' x 'column of commons',
* that way one can easily see all the modules that visual/ depends
* on by looking at the 'row of visual'.
*
* todo: I think Ndepend does the reverse. Also I think Ndepends uses
* a symetric matrix model where one can get more information by looking
* at both directions. In one direction one can know how many entities A is
* using in B, and in the other by how many entities in A some stuff in B
* are used. For instance one can see that A is using many different functions
* in B, and then can see that actually all those functions are used by
* only one thing in A, in which case it's a sign that maybe this function
* should be moved in B. This is done I think only when there is a clean
* layered archi. When there are cycles then NDepend uses another color
* for the cell.
*
* todo: coupling/cohesion metrics! the dsm can be helpful to visualize
* this? see patterns? use more colors?
*)typedm={matrix:intarrayarray;i_to_name:Graph_code.nodearray;name_to_i:(Graph_code.node,int)Hashtbl.t;(* which nodes are currently expanded *)config:config;}(* It's actually more a 'tree set' than a 'tree list' below
* when we pass the config to build(). Indeed it's build() which
* will order this set according to some partitionning algorithm
* that tries to "layer" the code.
* less: could reuse Common.tree2.
*)andtree=|NodeofGraph_code.node*treelistandconfig=treeletbasic_configg=Node(G.root,Graph_code.childrenG.rootg+>List.map(funn->Node(n,[])))letbasic_config_optigopti=Node(G.root,Graph_code_opti.childrenG.rootgopti+>List.map(funn->Node(n,[])))typeconfig_path_elem=|ExpandofGraph_code.node|FocusofGraph_code.node*deps_styleanddeps_style=|DepsIn|DepsOut|DepsInOuttypeconfig_path=config_path_elemlist(* We sometimes want to manually order certain entries in the matrix,
* especially when the code is a mess with cycles everywhere in
* which case the default partitionning algorithm does not help.
* The hashtbl maps string nodes to the ordered list of children
* we want. We use a hash and not a tree because at some point
* we may want to specify the order only for certain deeply
* nested directories in which case we will do a find -name "info.txt"
* to build all the partial constraints.
*)typepartition_constraints=(string,stringlist)Hashtbl.t(* let tasks = ref 16 *)(* Phantom types for safer array access between the graph_opti, dm, and
* the full matrix dm. Not really used, but could one day.
*)type'aidx=inttypeidmtypeigoptitypecell_coord=int*int(*****************************************************************************)(* Globals *)(*****************************************************************************)letverbose=reffalse(*****************************************************************************)(* Helpers *)(*****************************************************************************)letrecfinal_nodes_of_treetree=matchtreewith|Node(n,xs)->ifnullxsthen[n]elseList.mapfinal_nodes_of_treexs+>List.flattenlethashtbl_find_nodehn=tryHashtbl.findhnwithNot_found->(* pr2 (spf "PB: %s" (G.string_of_node n));*)(* raise Not_found *)failwith(spf"Not_found: %s"(G.string_of_noden))(*****************************************************************************)(* Display *)(*****************************************************************************)(* poor's man DSM visualizer (use codegraph for a real visualization) *)letdisplaydm=pr2_gendm;()(*****************************************************************************)(* Explain the matrix *)(*****************************************************************************)(* history:
* - iterate over all edges
* - iterate only on the children of i
* - use graph_opti instead of the memoized projection index
*)letexplain_cell_list_use_edges(i,j)dmgopti=letres=ref[]inletn_nodes=G2.nb_nodesgoptiinletigopti_to_idm=Array.maken_nodes(-1)indm.i_to_name+>Array.iteri(funidmnode->igopti_to_idm.(hashtbl_find_nodegopti.G2.name_to_inode)<-idm;);let(projected_parent_of_igopti:idmidxarray)=Array.maken_nodes(-1)inlet(iroot:igoptiidx)=hashtbl_find_nodegopti.G2.name_to_iG.rootinletrecdepthparentigopti=letchildren=gopti.G2.has_children.(igopti)inletidm=igopti_to_idm.(igopti)inletproject=ifidm=-1thenparentelseidminprojected_parent_of_igopti.(igopti)<-project;children+>List.iter(depthproject);indepth(-1)iroot;gopti.G2.use+>Array.iteri(funi2xs->letparent_i2=projected_parent_of_igopti.(i2)inxs+>List.iter(funj2->letparent_j2=projected_parent_of_igopti.(j2)inifparent_i2=i&&parent_j2=jthenCommon.push(gopti.G2.i_to_name.(i2),gopti.G2.i_to_name.(j2))res;));(*
let (src: igopti idx) = hashtbl_find gopti.G2.name_to_i dm.i_to_name.(i) in
let (dst: idm idx) = j in
let rec aux n1 =
let uses = gopti.G2.use.(n1) in
uses +> List.iter (fun n2 ->
let idm = igopti_to_idm.(n2) in
if idm = dst
then Common.push2 (gopti.G2.i_to_name.(n1), gopti.G2.i_to_name.(n2)) res;
);
let children = gopti.G2.has_children.(n1) in
List.iter aux children
in
aux src;
*)!res(*
let explain_cell_list_use_edges a b c =
Common.profile_code "DM.explain_cell" (fun () ->
explain_cell_list_use_edges2 a b c)
*)(*****************************************************************************)(* tree config manipulation *)(*****************************************************************************)letexpand_nodentreeg=letrecauxtree=matchtreewith|Node(n2,xs)->ifn=*=n2then(* less: assert null xs? *)letsucc=G.succnG.HasginNode(n2,succ+>List.map(funn->Node(n,[])))elseNode(n2,xs+>List.mapaux)inauxtreeletexpand_node_optintreeg=letrecauxtree=matchtreewith|Node(n2,xs)->ifn=*=n2then(* less: assert null xs? *)letsucc=Graph_code_opti.childrennginNode(n2,succ+>List.map(funn->Node(n,[])))elseNode(n2,xs+>List.mapaux)inauxtree(* To focus on a node we need to know its dependencies to filter
* the irrelevant one and so we need a dm passed as a parameter.
* This function is mainly used in a Model.config_of_path
* where we fold over an initial dm and given a path element
* expand or focus to get a new dm and so on.
*)letfocus_on_nodendeps_styletreedm=leti=hashtbl_find_nodedm.name_to_ininlet(deps:intlistref)=ref[]inletnb_elts=Array.lengthdm.matrixinforj=0tonb_elts-1doletto_include=matchdeps_stylewith|DepsOut->dm.matrix.(i).(j)>0|DepsIn->dm.matrix.(j).(i)>0|DepsInOut->dm.matrix.(i).(j)>0||dm.matrix.(j).(i)>0in(* we do || i = j because we want the node under focus in too, in the
* right order
*)ifto_include||i=jthenCommon.pushjdepsdone;(* old: this was not keeping the hierarchy (which can be a feature)
* Node (G.root, !deps +> List.rev +> List.map (fun i ->
* Node (hashtbl_find_node dm.i_to_name i, []))
* )
*)letrecauxtree=matchtreewith|Node(n2,[])->letj=hashtbl_find_nodedm.name_to_in2inifi=j||List.memj!depsthenSome(Node(n2,[]))elseNone|Node(n2,xs)->letxs=xs+>Common.map_filterauxinifnullxsthenNoneelseSome(Node(n2,xs))in(* should be a Some cos at least we have 'n' in the tree *)Common2.some(auxtree)(*****************************************************************************)(* Config path *)(*****************************************************************************)letstring_of_config_path_elem=function|Expandn->spf"Expand(%s)"(G.string_of_noden)|Focus(n,style)->spf"Focus%s(%s)"(matchstylewith|DepsIn->"<-"|DepsOut->"->"|DepsInOut->"<->")(G.string_of_noden)letstring_of_config_pathxs=xs+>List.mapstring_of_config_path_elem+>Common.join"/"(*****************************************************************************)(* Matrix analysis *)(*****************************************************************************)letis_dead_columnjdm=letmat=dm.matrixinlethas_user=reffalseinfori=0toArray.lengthmat-1doifmat.(i).(j)>0&&i<>jthenhas_user:=truedone;not!has_userletis_dead_lineidm=letmat=dm.matrixinletuse_stuff=reffalseinforj=0toArray.lengthmat-1doifmat.(i).(j)>0&&i<>jthenuse_stuff:=truedone;not!use_stuffletparents_of_indexesdm=letarr=Array.make(Array.lengthdm.matrix)[]inleti=ref0inletrecauxacctree=matchtreewith(* a leaf *)|Node(_,[])->arr.(!i)<-List.revacc;incri(* a node *)|Node(n,xs)->xs+>List.iter(aux(n::acc))inaux[]dm.config;arr(* ex: dist a/b/c to a/b/d/e should be ? *)letdistance_entity(i,j)arr=letxs=arr.(i)inletys=arr.(j)inletrecauxxsys=matchxs,yswith|[],[]->0|_,[]->1(* if it's a subentity of a brother, then distance should still be 0 *)|[],_->0|x::xs,y::ys->ifx=*=ythenauxxsyselse1inauxxsys(* less: more fine grained internal modules in package where can see what
* is the scope of the module. So can see stuff really important in
* a whole package because they are really used outside this package,
* so depth of escape > X. ===> remember max depth of escape
* 0 = same module, 1, brother, etc.
*)letis_internal_helperjdm=letmat=dm.matrixinletarr=parents_of_indexesdminlethas_users_outside_parent=reffalseinletparents=arr.(j)infori=0toArray.lengthmat-1doifmat.(i).(j)>0&&i<>j&&distance_entity(j,i)arr>0thenhas_users_outside_parent:=truedone;not!has_users_outside_parent&&(* the elements at the root can't have dependencies outside parents *)List.lengthparents>1letscore_upper_triangledmexclude_nodes=letscore=ref0inletexclude_idx=exclude_nodes+>List.map(funn->hashtbl_find_nodedm.name_to_in)infori=0toArray.lengthdm.matrix-1doforj=i+1toArray.lengthdm.matrix-1doif(List.memiexclude_idx)||(List.memjexclude_idx)then()elsescore:=!score+dm.matrix.(i).(j)donedone;!scoreletscore_downer_triangledmexclude_nodes=letscore=ref0inletexclude_idx=exclude_nodes+>List.map(funn->hashtbl_find_nodedm.name_to_in)infori=0toArray.lengthdm.matrix-1doforj=0toi-1doif(List.memiexclude_idx)||(List.memjexclude_idx)then()elsescore:=!score+dm.matrix.(i).(j)donedone;!scoreletscore_upper_triangle_nodesdm=letscore=Array.make(Array.lengthdm.matrix)0infori=0toArray.lengthdm.matrix-1doforj=i+1toArray.lengthdm.matrix-1doletv=dm.matrix.(i).(j)inscore.(i)<-score.(i)+v;score.(j)<-score.(j)+v;donedone;score+>Array.mapi(funiv->(dm.i_to_name.(i),v))+>Array.to_listletscore_upper_triangle_cellsdm=letres=ref[]infori=0toArray.lengthdm.matrix-1doforj=i+1toArray.lengthdm.matrix-1doCommon.push((i,j),dm.matrix.(i).(j))resdonedone;!res