123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841(**************************************************************************)(* *)(* This file is part of Frama-C. *)(* *)(* Copyright (C) 2007-2025 *)(* CEA (Commissariat à l'énergie atomique et aux énergies *)(* alternatives) *)(* *)(* you can redistribute it and/or modify it under the terms of the GNU *)(* Lesser General Public License as published by the Free Software *)(* Foundation, version 2.1. *)(* *)(* It 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 *)(* GNU Lesser General Public License for more details. *)(* *)(* See the GNU Lesser General Public License version 2.1 *)(* for more details (enclosed in the file licenses/LGPLv2.1). *)(* *)(**************************************************************************)openCil_typesopenMt_typesopenMt_threadmoduleUtilities=structletmk_translation_tbll=List.fold_left(funsmap(s1,s2)->Datatype.String.Map.adds1s2smap)Datatype.String.Map.emptyl;;(* Outputs are done in separate buffers then assembled together.
The following allows to maintain some kind of consistency
in buffer creations.
*)letdefault_buffer_size=2048;;letmk_buffer_formatter()=letb=Buffer.createdefault_buffer_sizeinb,Format.formatter_of_bufferb;;let_escape_stringspecial_chars=Str.global_replacespecial_chars"\\\\\\\\0";;letreplace_charsttables=letbuf=Buffer.create((String.lengths)*2)inString.iter(func->lets=Format.sprintf"%c"cinletts=tryDatatype.String.Map.findsttablewithNot_found->sinBuffer.add_stringbufts)s;buf;;end(** Module to produce HTML output *)moduleHtml=structletescape=Extlib.html_escapeletpretty_escapedppfmtv=lets=Format.asprintf"%a"ppvinlets=escapesinFormat.pp_print_stringfmtsletdot_escapes=lettranslation_table=Utilities.mk_translation_tbl(List.map(funs->(s,"_"))["&";"+";"[";"]";"."])inUtilities.replace_charstranslation_tables;;(* Formatting html with Format.formatters *)lethtml_stag_functions:Format.formatter_stag_functions=letmark_open_stagt=Format.sprintf"<%s>"(Extlib.format_string_of_stagt)andmark_close_stagt=lett=Extlib.format_string_of_stagtintryletindex=String.indext' 'inFormat.sprintf"</%s>"(String.subt0index)with|Not_found->Format.sprintf"</%s>"tandprint_open_stag_=()andprint_close_stag_=()in{Format.mark_open_stag=mark_open_stag;Format.mark_close_stag=mark_close_stag;Format.print_open_stag=print_open_stag;Format.print_close_stag=print_close_stag;};;typehtml_page={page_title:string;page_name:string;(* the buffer contains the html code of the page *)page_buffer:Buffer.t;(* formatter of the previous buffer to use with Format *)page_fmt:Format.formatter;};;letmk_html_pagetitlename=letb,fmt=Utilities.mk_buffer_formatter()in{page_title=title;page_name=name;page_buffer=b;page_fmt=fmt;};;lethtml_fnamehtml_page=escapehtml_page.page_name^".html";;typehtml_div={title:string;contents:Buffer.t;div_fmt:Format.formatter;};;letempty_string=""letmk_divs=letb,fmt=Utilities.mk_buffer_formatter()inFormat.pp_set_formatter_stag_functionsfmthtml_stag_functions;Format.pp_set_tagsfmttrue;{title=s;contents=b;div_fmt=fmt;};;letpp_divfmtdiv=Format.pp_set_formatter_stag_functionsfmthtml_stag_functions;Format.pp_set_tagsfmttrue;Format.fprintffmt"@[@{<div class=\"\"><h3>@ %s</h3>@ %s @}@]@?"div.title(Buffer.contentsdiv.contents);;moduleBiassoc(K:Datatype.S_with_collections)=struct(* Bidirectional association tables *)type'at={to_id:'aK.Hashtbl.t;from_id:('a,K.t)Hashtbl.t;}letmkn={to_id=K.Hashtbl.createn;from_id=Hashtbl.createn;}endmoduleTable(Row:Datatype.S_with_collections)(Col:Datatype.S_with_collections)=structmoduleRows=Biassoc(Row)moduleCols=Biassoc(Col)type'ahtml_table={rows:intRows.t;columns:intCols.t;tbl_contents:'aarrayarray;row_size:int;col_size:int;}letmkrow_listcol_list=letrow_length=List.lengthrow_listinletrows=Rows.mkrow_lengthinletcol_length=List.lengthcol_listinletcols=Cols.mkcol_lengthinList.iteri(funik->Row.Hashtbl.addrows.to_idki;Hashtbl.addrows.from_idik;)row_list;List.iteri(funik->Col.Hashtbl.addcols.to_idki;Hashtbl.addcols.from_idik;)col_list;{rows=rows;columns=cols;tbl_contents=Array.make_matrixrow_lengthcol_lengthempty_string;row_size=row_length;col_size=col_length;};;(* Mark a set of actions according to a marking function
in a HTML table
*)letmark_action_setmark_funtableaction_idtbl=Col.Hashtbl.iter_sorted(funkaset->tryletcol=Col.Hashtbl.findtable.columns.to_idkinEventsSet.iter(funa->letid,mark=mark_funainletrow=Row.Hashtbl.findtable.rows.to_ididintable.tbl_contents.(row).(col)<-table.tbl_contents.(row).(col)^mark;)asetwith|Not_found->assertfalse)action_idtbl(* Pretty print a html table *)letpretty~pp_row_head~pp_col_head~pp_cell~caption~legendfmttable=letpp_row_head=pretty_escapedpp_row_headinletpp_col_head=pretty_escapedpp_col_headinletpp_cell=pretty_escapedpp_cellinletpp_rowfmti=letrow=tryHashtbl.findtable.rows.from_idiwith|Not_found->Mt_options.fatal"@[Row %d not found@]@."iinletpp_cellsfmtcell_array=Array.iter(Format.fprintffmt"@ @[@{<td class=\"plop\">@ %a@ @}@]"pp_cell)cell_arrayinFormat.fprintffmt"@[<v 1>\
@{<tr>@ \
@{<th>%a@}\
%a\
@}\
@]"pp_row_headrowpp_cellstable.tbl_contents.(i)inletrecpp_rowsfmti=ifi=table.row_sizethenFormat.fprintffmt""elseFormat.fprintffmt"@[<v 0>%a@ %a@]"pp_rowipp_rows(i+1)inletpp_headersfmt=letrecaux_pp_hdrfmti=ifi=table.col_sizethenFormat.fprintffmt""elseFormat.fprintffmt"@ @{<th>%a@}%a"pp_col_head(Hashtbl.findtable.columns.from_idi)aux_pp_hdr(i+1)inFormat.fprintffmt"@[<v 1>@{<tr>@ \
@{<td>%s@}%a\
@}@]"legendaux_pp_hdr0inFormat.pp_set_tagsfmttrue;Format.pp_set_formatter_stag_functionsfmthtml_stag_functions;Format.fprintffmt"@[<hov 1>@[@{<caption>%s@ @}@ @[%t@]@ %a@]@]@?"captionpp_headerspp_rows0endmoduleLockTable=Table(Mutex)(Thread)moduleMutexTable=Table(Mutex)(Thread)moduleQueueTable=Table(Mqueue)(Thread)(* Generate the set of lock taken in the program by all threads/processes
And a hash table associating threads to locking procedures (take, release
...)
*)letgen_locks_summaryth_list=letlock_table=Thread.Hashtbl.create(List.lengthth_list)inletlockset=List.fold_left(funlocksetth->letth_lockset=refEventsSet.emptyinletglobal_lockset=Trace.fold'th.th_amap(funactionlockset->matchactionwith|MutexReleaseid|MutexLockid->th_lockset:=EventsSet.addaction!th_lockset;Mutex.Set.addidlockset|_->lockset)locksetinThread.Hashtbl.addlock_tableth.th_eva_thread!th_lockset;global_lockset)Mutex.Set.emptyth_listinifMutex.Set.is_emptylocksetthenNoneelsebeginletlock_olist=Mutex.Set.elementslockset|>List.map(funm->Mutex.labelm,m)|>List.fast_sort(fun(s1,_)(s2,_)->String.compares1s2)|>List.mapsndinSome(lock_table,LockTable.mklock_olist(List.map(funth->th.th_eva_thread)th_list))end;;(* Generate the set of fifos used in the program
Mark the uses in a html table
Also yields a hash table id -> fifo uses
*)letgen_mqueues_summaryth_list=letmq_table=Thread.Hashtbl.create(List.lengthth_list)inletqueue_set=List.fold_left(funqueue_setth->letth_queue_set=refEventsSet.emptyinletglobal_queue_set=Trace.fold'th.th_amap(funactionqueue_set->matchactionwith|SendMsg(q,_)|CreateQueue(q,_)|ReceiveMsg(q,_,_)->th_queue_set:=EventsSet.addaction!th_queue_set;Mqueue.Set.addqqueue_set|_->queue_set)queue_setinThread.Hashtbl.addmq_tableth.th_eva_thread!th_queue_set;global_queue_set)Mqueue.Set.emptyth_listin(* Returns mothing when there is no queue in the program *)ifMqueue.Set.is_emptyqueue_setthenNoneelsebeginletqueue_olist=Mqueue.Set.elementsqueue_set|>List.map(funm->Mqueue.labelm,m)|>List.fast_sort(fun(s1,_)(s2,_)->String.compares1s2)|>List.mapsndinassert((Thread.Hashtbl.lengthmq_table)>0);Mt_options.debug"%d queues found@."(Thread.Hashtbl.lengthmq_table);Some(mq_table,QueueTable.mkqueue_olist(List.map(funth->th.th_eva_thread)th_list));end;;(* Columns are thread name, rows are locks *)letmark_lock_actions=MutexTable.mark_action_set(funaction->matchactionwith|MutexReleasem->m,"V"|MutexLockm->m,"P"|_->assertfalse(* This action set is generated by gen_locks_summary
and should only containt lock-related constructors
*));;letmark_mqueue_actions=QueueTable.mark_action_set(funaction->matchactionwith|SendMsg(id,_)->id,"S"|ReceiveMsg(id,_,_)->id,"R"|CreateQueue(id,_)->id,"C"|_->assertfalse(* This action set is generated by gen_mqueues_summary
and should only containt queue-related constructors
*));;(* Generate the html table for lock take/release actions *)letmk_locks_summarydivth_list=letb,fmt=div.contents,div.div_fmtinFormat.pp_set_tagsfmttrue;matchgen_locks_summaryth_listwith|None->b|Some(lock_table,html_table)->mark_lock_actionshtml_tablelock_table;letpp_table=LockTable.pretty~pp_row_head:Mutex.pretty~pp_col_head:Thread.pretty~pp_cell:Format.pp_print_string~caption:"P = lock taken, V = lock released"~legend:"uses lock ←<br/> ↓"inFormat.fprintffmt"@[<v 1>\
@{<h3>%s@}@ \
@{<table>@ %a@ @}</table>\
@]@?"div.titlepp_tablehtml_table;b;;(* Generate the html table for write/receive fifo summaries *)letmk_mqueues_summarydivth_list=matchgen_mqueues_summaryth_listwith|None->div.contents|Some(queue_idtbl,html_table)->(* Only print when there is something to be said *)Format.pp_set_tagsdiv.div_fmttrue;mark_mqueue_actionshtml_tablequeue_idtbl;letpp_table=QueueTable.pretty~pp_row_head:Mqueue.pretty~pp_col_head:Thread.pretty~pp_cell:Format.pp_print_string~caption:"R = queue read, S = queue written, C = queue created"~legend:"uses lock ←<br/> ↓"inFormat.fprintfdiv.div_fmt"@[<v 1>@ \
@{<h3>%s@}@ \
@{<table>@ %a@ @}</table>@]@?"div.titlepp_tablehtml_table;div.contents;;;(* Output a small global summary :
number of threads and their names
*)letmk_global_summaryth_listpage_table=letb,fmt=Utilities.mk_buffer_formatter()inletth_buf,th_fmt=Utilities.mk_buffer_formatter()inFormat.pp_set_tagsfmttrue;Format.pp_set_tagsth_fmttrue;Format.fprintfth_fmt"@[<v>";List.iter(funth->Format.fprintfth_fmt"@[ <li><a href=\"%s\">%a</a></li>@]@ "(html_fname(Thread.Hashtbl.findpage_tableth.th_eva_thread))(pretty_escapedThreadState.pretty)th;)th_list;Format.fprintfth_fmt"@]@.";Format.fprintffmt"@[<v 1>@[\
@{<h1> Summary @}@ \
<br/>@ \
This program has %d thread(s)@ \
@ @{<ul>@ %s @}@]@]@?"(List.lengthth_list)(Buffer.contentsth_buf);b;;(* Some defaults *)letdefault_dir="html_summary";;letmain_page_name="index";;letfooter_links=mk_div"Go to thread";;letstmt_links=Printf.sprintf"sid%d"s.sid(* Turns unicode mode off and returns original value *)letsuspend_unicode()=letunicode=Kernel.Unicode.get()inKernel.Unicode.off();unicode;;letappend_file~input~output~name=letcreate=not(Sys.file_existsoutput)inletprint_headercout=Out_channel.output_stringcout"// Concatenated dot files. \
Generate all graphs with `dot -Tpng -O file.dot`\n\
// They will be named file.dot.png, file.dot.2.png, etc.\n\n";inletcopycincout=ifcreatethenprint_headercout;Printf.fprintfcout"// Graph for %s\n"name;In_channel.input_allcin|>Out_channel.output_stringcout;Out_channel.output_stringcout"\n\n"inletwith_open_in=In_channel.with_open_textinputinletflags=[Open_creat;Open_text;Open_wronly;Open_append]inletwith_open_out=Out_channel.with_open_genflags0o666outputintrywith_open_out(funcout->with_open_in(funcin->copycincout))withe->Mt_options.error"Error while appending dot file %s to %s: %s"inputoutput(Printexc.to_stringe)letmk_graph_imgth=letunicode=suspend_unicode()inletf_stmts=Format.sprintf"code.html#%s"(stmt_links)inletthread_name=Thread.labelth.th_eva_thread|>Mt_lib.sanitize_filenameinlettmp_file,otmp=Filename.open_temp_file(thread_name^"-")".dot"inletfmt=Format.formatter_of_out_channelotmpinMt_cfg.dot_fprint_graphfmtth.th_cfgf_stmt;close_outotmp;ifnot(Mt_options.ConcatDotFilesTo.is_empty())thenbeginletname=Thread.labelth.th_eva_threadinletoutput=(Mt_options.ConcatDotFilesTo.get():>string)inappend_file~input:tmp_file~output~nameend;letdot_output_format="svg"inletlink_fname=(Format.asprintf"%s.%s"thread_namedot_output_format)inletoutput_file=Filename.concatdefault_dirlink_fnameinletargs=["-Tsvg";tmp_file;"-o";output_file]inletfails=Mt_options.error"%s when generating graph for thread %a. \
Run 'dot %s' to restart generation"sThreadState.prettyth(String.concat" "args)inbegintryletret=Command.spawn~timeout:60"dot"argsinmatchretwith|Unix.WEXITED0->Mt_options.debug"remove %s\n"tmp_file;Sys.removetmp_file|Unix.WEXITEDcode->fail(Printf.sprintf"Error (code %d)"code)|Unix.WSIGNALEDid->fail(Printf.sprintf"Signal %d"id)|Unix.WSTOPPEDid->fail(Printf.sprintf"Process stopped (signal %d)"id)with|Sys_errors->fail(Printf.sprintf"Error (%s)"s)|Async.Cancel->fail"Timeout or user interruption"end;Kernel.Unicode.setunicode;link_fname;;letmk_thread_graphth_list=letmoduleThreadInheritanceGraph=structinclude(Graph.Imperative.Digraph.Concrete(Thread))letgraph_attributes_=[]letdefault_vertex_attributes_=[]letvertex_namev=lets=Format.asprintf"%a"Thread.prettyvinBuffer.contents(dot_escapes)letvertex_attributesv=lets=Format.asprintf"%a"Thread.prettyvin[`Label(Mt_lib.escape_non_utf8s)]letget_subgraph_=Noneletdefault_edge_attributes_=[`Style(`Solid);]letedge_attributes_=[]endinletgraph=ThreadInheritanceGraph.create~size:(List.lengthth_list)()inList.iter(funth->matchth.th_parentwith|None->ThreadInheritanceGraph.add_vertexgraphth.th_eva_thread;|Someparent->ThreadInheritanceGraph.add_edgegraphparent.th_eva_threadth.th_eva_thread)th_list;letmoduleTGDot=Graph.Graphviz.Dot(ThreadInheritanceGraph)inletunicode=suspend_unicode()inletname="thread_inheritance_graph"inlettmp_file,otmp=Filename.open_temp_filename".dot"inMt_options.debug"Open %s for writing@."tmp_file;letfmt=Format.formatter_of_out_channelotmpinTGDot.fprint_graphfmtgraph;close_outotmp;letdot_output_format="svg"inletlink_fname=Format.sprintf"%s.%s"namedot_output_formatinletoutput_file=Filename.concatdefault_dirlink_fnameinletcmd=Format.sprintf"dot -T%s '%s' -o '%s'"dot_output_formattmp_fileoutput_fileinletret=Sys.commandcmdinifret<>0thenMt_options.error"Something bad happened when running %s"cmd;Mt_options.debug"remove %s\n"tmp_file;Sys.removetmp_file;Kernel.Unicode.setunicode;link_fname;;letmk_thread_graph_divdivth_list=letb,fmt=div.contents,div.div_fmtinletimg=mk_thread_graphth_listinFormat.fprintffmt"@[<v 0>@{<div> \
@{<h3>%s@}\
@{<object data=\"%s\" width=\"700\" \
height=\"250\" type=\"image/svg+xml\"> @}\
@{<a href=\"%s\"> Direct link @}\
@}@]@?"div.titleimgimg;b;;;letpp_image_linkfmtth=letimg=mk_graph_imgthinFormat.fprintffmt"@{<embed src=\"%s\" width=\"700\" \
height=\"600\" type=\"image/svg+xml\" />\
<br /> \
<a href=\"%s\" >Direct link</a>"imgimg;;letpp_thread_detailshtml_pagemain_pageth=letfmt=html_page.page_fmtinFormat.pp_set_tagsfmttrue;Format.fprintffmt"@[<v 1>@ \
@[<v 1>@{<div>@ \
@{<h1><a name=\"%s\">%a</a>@}\
@]@ \
@[<hov 1>@{<div class=\"graph\">%a@}@]@ \
@}\
<br/>@ %a@ \
<br/>@ @{<h3 class=\"back\">Back to @{<a href=\"%s\">index@}@}\
@]@]@?"(escapehtml_page.page_name)ThreadState.prettythpp_image_linkthpp_divfooter_links(html_fnamemain_page);Format.pp_print_flushfmt();;;(* Lazy to avoid messages when mthread is not launched, or the css
not needed *)letcss_content=lazy(letcss_file=(Mt_options.MThread.Share.get_file"mthread.css":>string)intryletic=open_incss_fileinletic_length=in_channel_lengthicinletb=Buffer.createic_lengthinBuffer.add_channelbicic_length;close_inic;Buffer.contentsbwithSys_error_->Mt_options.warning"Cannot open mthread css '%s'"css_file;"");;letpp_pagepage=letfile=Filename.concatdefault_dirpage.page_name^".html"inMt_options.debug"Open %s@."file;letofile=open_outfileinletfmt=Format.formatter_of_out_channelofileinFormat.pp_set_formatter_stag_functionsfmthtml_stag_functions;Format.pp_set_tagsfmttrue;Format.fprintffmt"@[<v 1>\
<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\"\
\"http://www.w3.org/TR/html4/strict.dtd\">@ \
@{<html>@ \
@{<head>@ \
@{<title>%s@}@ \
<meta content=\"text/html; charset=iso-8859-1\" \
http-equiv=\"Content-Type\">@ \
@{<style type=\"text/css\">%s@}@}@ \
@{<body>@ %s@ \
@}@}@}@]@?"page.page_title(Lazy.forcecss_content)(Buffer.contentspage.page_buffer);close_outofile;;;letmk_main_pagepagepage_tableth_list=(* Do the main page *)letbuf_init,_fmt_init=page.page_buffer,page.page_fmtinletbuf_append=Buffer.add_bufferbuf_initin(* Generate the main page *)Buffer.add_stringbuf_init"<!--(* Generated my mthread *)-->";buf_append(mk_global_summaryth_listpage_table);(* Graph for thread creation *)buf_append(mk_thread_graph_div(mk_div"Thread creation graph")th_list);(* Table for lock uses *)buf_append(mk_locks_summary(mk_div"Lock operations")th_list);(* Table for queue uses *)buf_append(mk_mqueues_summary(mk_div"Queue operations")th_list);;;classtagPrinterClass=object(self)inheritPrinter.extensible_printer()assupermethod!next_stmtnextfmtcurrent=Format.fprintffmt"@{<span id=\"sid%d\">%a@}"current.sid(super#next_stmtnext)currentmethod!stmtkindsattrsfmtskind=letprint_as_is=Cil_printer.state.Printer_api.print_cil_as_isin(* Ugly hack to correctly print while(1) conditionals *)(matchskindwith|Loop_->Cil_printer.state.Printer_api.print_cil_as_is<-true|_->());super#stmtkindsattrsfmtskind;Cil_printer.state.Printer_api.print_cil_as_is<-print_as_ismethod!varinfofmt(v:varinfo)=letvclass=ifAst_types.is_funv.vtypethen"varinfo_fun"else"varinfo"inFormat.fprintffmt"@{<a class=\"%s\" href=\"#vid%d\">%a@}"vclassv.vidself#varnamev.vnamemethod!vdeclfmt(v:varinfo)=Format.fprintffmt"@{<span class=\"vdecl\" id=\"vid%d\">%a@}"v.vidsuper#vdeclv(*
method! global fmt (g:global) =
match g with
| GVarDecl (v, _) when v.vstorage <> Extern -> ()
| _ -> super#global fmt g
*)endletast_to_htmlfile=letpage=mk_html_page"Source code"fileinletfmt=page.page_fmtinFormat.pp_set_formatter_stag_functionsfmthtml_stag_functions;Format.pp_set_tagsfmttrue;letpp=newtagPrinterClassinFormat.fprintffmt"<pre>@,%a</pre>@?"pp#file(Ast.get());pp_pagepage;;letoutput_threadsanalysis=letth_list=List.filtershould_compute_thread(threadsanalysis)inletpage_table,add_page,find_page=letmodulePageTable=Thread.Hashtblinletpage_table=PageTable.create(List.lengthth_list)inpage_table,PageTable.addpage_table,PageTable.findpage_tablein(tryUnix.mkdirdefault_dir0o777;with_->());letmain_page=mk_html_page"Summary"main_page_namein(* Initialize one page with a buffer, a link name, a formatter
for every thread
*)List.iter(funth->letthread_name=Format.asprintf"%a"ThreadState.prettythinlethtml_page=mk_html_page(Format.asprintf"Summary for thread %s"thread_name)(Format.asprintf"%a"ThreadState.prettyth)inadd_pageth.th_eva_threadhtml_page;Format.pp_set_formatter_stag_functionshtml_page.page_fmthtml_stag_functions;Format.pp_set_tagshtml_page.page_fmttrue;)th_list;(* Do back links *)letmk_footer_links()=Format.pp_set_formatter_stag_functionsfooter_links.div_fmthtml_stag_functions;Format.pp_set_tagsfooter_links.div_fmttrue;Format.fprintffooter_links.div_fmt"@[ <ul class=\"horizontal\">@]";List.iter(funth->lethpage=find_pageth.th_eva_threadinFormat.fprintffooter_links.div_fmt"@[@{<li class=\"horizontal\">@{<a href=\"%s\">@ %s@}@}@]"(html_fnamehpage)(escapehpage.page_name))th_list;Format.fprintffooter_links.div_fmt"@[ </ul>@]@.";inmk_footer_links();(* Print pages *)List.iter(funth->letdetails=find_pageth.th_eva_threadinpp_thread_detailsdetailsmain_pageth)th_list;mk_main_pagemain_pagepage_tableth_list;(* Generate per thread files *)Thread.Hashtbl.iter_sorted(fun_thhtml_page->pp_pagehtml_page)page_table;pp_pagemain_page;ast_to_html"code";;;endmoduleEva_results=struct(* Fuses the value analysis results for each thread, reprefix them by
a fresh kernel function to have nice callstacks, fuse all the
results, and set the result as Value's results. *)letdisplayanalysis=letths=analysis.all_threadsinletaux_theva_ththacc=matchth.th_value_resultswith|None->acc(* Analysis skipped *)|Someresults->letchangecs=Callstack.{cswiththread=Thread.ideva_th}inletresults'=Eva_results.change_callstackschangeresultsinresults'::accinletall_results=Thread.Hashtbl.foldaux_thths[]inmatchall_resultswith|[]->Mt_options.error"No results recorded. Nothing to display"|r::qr->letall=List.fold_leftEva_results.mergerqrinEva_results.set_resultsallend