Professional Documents
Culture Documents
Welcome to the third issue of SPITWAD. This issue highlights the new Windchill profiling tool, available with X05 build 40. Please take the time to read the lead article by Jiten Kirloskar. We hope this becomes a developer tool as commonly used as the Java compiler. This issue also includes a nice discussion by John Gibson on Windchill architecture constructs supporting federations. Please send comments and suggestions to <SPITWAD Feedback>. We willingly accept ideas - and submissions - for inclusion in future newsletters. In this issue you will find: Articles 1. Lead Article - Windchill Profiler Tool 2. Spotlight Article Windchill Federation Columnists 1. The Practical Developer 2. "The Pointer A Developers Bird Dog" 3. The Intrepid Trainer Letters to the Editor
As of X05 build 39, time consuming operations have already been made profilable and are available for profiling OOTB. These operations are:1. SQL statement Displays call stack of SQL statements sorted on the SQL statement count, SQL statement execution time of bind parameters count 2. Info*Engine invokes Displays call stack of Task.invoke() calls sorted on query string count or query parameters count 3. CacheManager allows profiling cache hits, misses, update, removes between servers. Displays stack of RemoteCacheManager calls sorted on cache count or hits/misses/updates/deletes count or cache value count. 4. PersistInfo Displays call stack of PersistInfo serialization sorted on readExternal/WriteExternal call count or classid count
Note: If the Windchill server is not running, the client tool starts but no operations will be listed under the Operations menu. However, a profiler output file can be loaded for analysis from the File menu. The following properties can be set in wt.properties wt.profiler.profTimeout: Time is seconds after which profiling is disabled automatically. A safeguard feature. wt.profiler.maxProfHits: The number of calls reported to the profiler service after which the profiler data is flushed to the client tool. This limits the method server heap. Default value is 20 wt.profiler.verbose: Profiler service verbose mode.
How to analyze the display call stack in the profiler client tool?
1. Select a thread which reported a lot of calls 2. For SQL profiling, select the Bind Parameters node
_________________________________________________________________________ Volume 1, Number 3 - Fall 2004
3. Click on the reverse call stack button. Most of the performance bottlenecks will be seen in this mode. 4. Start expanding the nodes and search for frames showing calls being made from the same function a few lines apart as seen in the example below and check if some can be eliminated.
A, B and C will be used as display strings for this operation in the Operations menu of the client GUI tool. 2. Parameter A is mandatory and indicates that your operation will report a String operation data in step 5(Astr) eg SQL statement.
3. Parameter B is optional and indicates that your operation will report a integer cost value in step 5(Bint) eg the execution time for the SQL statement. Pass null if this parameter will not be reported 4. Parameter C is optional and indicates that your operation will report a string operational data in step 5(Cstr) eg SQL statement bind parameters. Pass null if this parameter will not be reported.
3. Define a listener key which controls the boolean flag defined in step 1.
<myProfList> = new ProfilingListener() { public void setEnabled(boolean enabled) throws WTException { <myProfFlag> = enabled; System.out.println( <myProfFlag> profiling enabled:" + enabled); }};
4. Register the adapter/listener combination with the profiling service. This registration will cause your operation to get listed under the Operations menu of the client GUI tool.
AdapterListMap.getInstance().regProfOperation (<myProfKey>, <myProfList>);
5. Add the following instrumentation hook in your service/operation to actually report the profiling information to the profiling service. Without this statement, nothing will get displayed in the client tool even if the operation is enabled in the Operations menu.
If(<myProfFlag> ){ ProfilingHelper.repProfilingData(<myProfKey>, String Astr, int Bint, String Cstr) }
If null values are passed for B and C in step 2, then Bint and Cstr will be ignored. If B or C is not null in step 2, then Bint or Cstr have to passed in the above call respectively.
Post Feedback
What is Federation?
Federation is when multiple enterprise information systems work cooperatively with one another. Its foolish to think that everything important to a product development business will end up in a single information system, let alone one based on Windchill. Even a very large Windchill system may be just one of many information systems that are critical to the business of product development. Each system can have its own database schema and business transactions, and federation is connecting them to satisfy the needs of the business. Federation is not about making multiple systems appear as one. That would be one system with a distributed architecture. Rather, federation is creating something new by connecting independent systems in a useful way. You can think of federation as building big picture applications out of little picture systems and the data they own. Examples of federation include the Windchill ESI module, which automates the process of releasing product designs from Windchill into ERP systems, and Windchills own use of LDAP servers as external repositories of user and group information.
tutorial on Info*Engine is beyond the scope of this article, but the concepts that are key to the big picture of Windchill federation are covered here. These include federated type identifiers (FTIs), unique federation identifiers (UFIDs), repositories, task delegates, and the support within Windchills object model for recording associations between federated systems. Although delivered as part of each Windchill system, Info*Engines system intermediation operates as a layer that is logically above the rest of Windchill. It has a distributed architecture where application adapters and tasks run near or within the systems they interface to. Whereas each information system has its own private data repository, usually a RDBMS, Info*Engine uses LDAP accessible directory servers as repositories for the information defining federated applications so it can be shared by all the distributed Info*Engine components.
LDAP
Database System A
Database System B
The first step in understanding Windchill federation is to realize that Windchills object model is not the common object model in a federation, but only one of several private implementation object models. Not all Windchill object types or object instances will be exposed within the federated schema, and Windchill Object Identifiers (OIDs) are not sufficient to identify instances of objects visible in the federated schema. The name of an abstract object type visible in the federated schema is known as a Federated Type Identifier (FTI). Instances of objects visible in the federated schema are addressable by a Unique Federation Identifier (UFID). Coming to understand these two terms, what they mean and just as importantly, what they dont mean, is perhaps the second step in understanding Windchill federation. FTIs and UFIDs are necessary because the information schema of the information systems in a federation can vary significantly. The systems may be products of different software vendors, or different versions of the same product, or just be highly customized deployments of the same version of the same product. In any case, each system may refer to similar object types within its schema using different names, and object instances may be uniquely identified using different criteria. FTIs and UFIDs provide a common language to allow different systems to exchange information. This is the language of the system intermediation layer (Info*Engine). All information exchanges between federated systems take place through this layer.
System A
System B
Database
Database
Repositories
In Info*Engine and Windchill nomenclature, each information system being federated is called a repository. Repositories are classified by type, where repositories of the same type support the same federated types and operations. E.g. Repository type com.ptc.windchill. Info*Engine stores repository and repository type information in LDAP accessible directory servers. Repository type entries act as the roots of subtrees containing type identifier entries for the set of federated types that such repositories support, and for each of these type entries, the operations they implement is represented as a subtree of command delegate definition entries. The individual repository entries then identify the Info*Engine adapter and task processor definitions used to invoke operations for that repository instance.
Repository com.ptc.ptcnet.jdgibson03l
Task Delegates
The abstract operations on federated types, also called actions or commands, are implemented by Info*Engine task delegates. The Info*Engine tasks implementing
_________________________________________________________________________ Volume 1, Number 3 - Fall 2004
each command in a repository are identified as an attribute of the LDAP command delegate definition entries for its repository type. In order for operations on an object to be dispatched to the right task delegate, Info*Engine must know what repository owns the object and what federated type it represents. Fortunately, each Element (or TypeInstance) in an Info*Engine group object (the collections of objects that are the inputs and outputs of Info*Engine webjects and tasks) contain metadata that specify its UFID and FTI. These are combined with the abstract action name to select the task delegates to be executed. Many repositories can support the same FTIs, so the repository to dispatch a command to must come from clues in the UFID.
So UFIDs are basically pointers to objects in remote repositories, right? Wrong. Are you sure youve been paying attention? A UFID is a reference to an abstract object in the federated schema, which is independent from all the repositories. This is where things get a bit tricky. UFIDs are object identifiers in the federated schema. They define a namespace for the system intermediation layer. Each federated information system has its own local namespace corresponding to its local schema. Binding a local object to a UFID binds it to an object in the federation namespace and indirectly (via the UFID) identifies a repository with which to execute federated commands (via task delegates) for that object. Although it may sometimes seem like systems federate directly to one another using UFIDs as remote object references, the systems are actually indirectly connected via the system intermediation layer and its federated schema. In this articles illustrations, this is shown by the information flow between system being up from one system into the system intermediation layer and back down into another from there rather than being straight across.
System A
System B
Local Id Namespace Concrete App Schema Persistent objects Objects relate to UFIDs
Database
Database
Recording associations spanning systems involves relating local objects to UFIDs. An object in one systems schema may correspond one-to-one with an object in the federated schema, but there are still two layers and namespaces involved, one an abstract interface and the other a concrete implementation. This separation allows multiple federation scenarios to be supported, including: Replication - one object in federated object model has replicas stored in many of the repositories.
Distribution - one object in federated object model is an aggregate (composition) of objects in different repositories. Association - objects in the federated object model have associations between them recorded in one or more repositories.
The federation related interfaces and classes that are of more interest to Windchill application developers are those that capture the relationships between objects in the local schema and objects in the federated schema.
FederatedLink Class
FederatedLink is a weakly-typed link between Windchill objects and LightweightProxy instances. Windchills strongly typed links are modeled as associations between specific strongly typed classes in the local schema, which generally does not include LightweightProxy as a valid role participant. FederatedLink exists to support capturing federated associations that cannot otherwise be represented by strongly typed links to other Federatable class instances.
Since this class is otherwise weakly typed (i.e. instances can represent any valid Windchill association), there is an attribute that records the actual link type. This attribute allows the link to masquerade as the Windchill Link type when Info*Engine navigates links between objects.
DCA
Info*Engine Tasks
System A
System B
System C
Database
Database
Database
In federation by lightweight proxy, the Federatable object is a LightweightProxy instance bound to UFID, and the associations are represented by FederatedLink objects. In federation by heavyweight proxy, the Federatable object is a normal object in the Windchill object model that is bound to a UFID, and the associations are the normal associations allowed in the local schema. Systems may federate using a combination of heavyweight and lightweight proxies. Although performance may play a role, choosing the appropriate representation of a remote object within a system depends largely on how visible that object needs to be to the native logic of that system. A heavyweight object is a native, first-class object in the local schema. It participates directly in the internal operations of that system, and is visible to the systems native applications. A lightweight proxy, on the other hand, is just a placeholder to allow persisting FederatedLink associations. These links and their LightweightProxy objects are generally visible only to federated applications build on top of the system intermediation layer, Info*Engine. Native applications, looking only at the local systems schema, dont see these objects the same as they do their heavyweight cousins. Only Info*Engine webjects dealing with association navigation will recognize the FederatedLink objects and allow navigating between systems based on the UFIDs bound to LightweightProxy objects. For example, where navigating a WTPartUsageLink natively in a Windchill service will only see real WTPartUsageLinks and therefore find only other local part objects, the Query-Tree or Query-Links Info*Engine webjects in a Info*Engine task can see both real WTPartUsageLink and any FederatedLink instances pretending to be WTPartUsageLinks, so they can return objects owned by multiple systems. Persisting local associations to remotely owned federated objects allows a system to implicitly support federated applications that navigate from a locally owned object to remotely owned objects because locally executing Info*Engine query webjects will return the UFIDs of remote objects. Recursive Info*Engine webjects such as Query-Tree can then implicitly follow federated structures because it will recurse to the repository responsible for the returned UFIDs. It is also possible to support such operations even when the local system does not have locally persisted associations to federated objects, but in this case, the task delegates needing to navigate to remote objects must be customized to execute query webjects in multiple repositories explicitly in order to see if they contain persisted associations to the local objects UFID. Recursive Info*Engine webjects such as Query-Tree will not recurse beyond the local repository, however.
Query-Tree Query-Links
001 UFID: 1@A 002 UFID: 2@A 003 UFID: 3@B 003 UFID: 3@B Heavy WTPart Proxy 004 UFID: 4@B System B
WTPartUsageLink
System A
In the above example, only a single heavyweight proxy is involved and it is persisted in system A. System A native applications can see that part 001 uses part 002 and 003, and likewise, they can see that part 003 is used-by part 001 if navigating in that direction. The Info-Engine Query-Tree webject following uses relationships starting at part 001 will return the full tree, including part 004. However, since nothing is persisted in system B regarding the uses relationship between part 001 and part 003, System B native applications will not acknowledge that part 003 is used-by part 001. Thats reasonable for system B native applications, but what about a federated application thats trying to explore the big picture. If it starts at part 004, a QueryTree in the used-by direction will only go as far as part 003. Why? Because querying for uses relationships to part 003 is performed in the repository of record responsible for part 003, which is system B. To support bi-directional structure navigation by federated applications, a lightweight proxy for 001 and a federated link representing the uses relationship should exist in system B.
Query-Tree Query-Links LightweightProxy FTI: WCType|wt.part.WTPart FederatedLink FTI: WCType|wt.part.WTPartUsageLink 003 UFID: 3@B 003 UFID: 3@B 004 UFID: 4@B System B
System A
Both of the above approaches are valid. The degree to which systems need to maintain redundant persistent associations and proxies in their local repositories depends on the requirements of the federated application(s) being supported. Simple federated search spanning multiple systems can be achieved without any redundancy if the systems are otherwise self-contained. However, once associations to or from federated objects need to be managed within the context of individual systems, there is a good chance that objects will be redundantly represented in more than one system, either by lightweight or heavyweight proxies. Of course, having local objects that are logically owned by a remote system creates a data integrity and synchronization issue. In the case of heavyweight proxies, there is the issue of keeping replicated metadata synchronized with the owning system. In the case of federated links that mirror associations owned by another system, there is the issue of keeping their existence synchronized with the owning system. To enable maintenance of inter-system data integrity, Info*Engine tasks can be triggered by native Windchill service events. For example, the creation or deletion of a WTPartUsageLink to part 003 in system A can trigger a task that creates or deletes the corresponding lightweight proxy and federated link in system B. Rigorously maintaining referential integrity and replicated metadata between loosely coupled, independent managed systems is not trivial. Simple approaches can try to perform the work synchronously and fail operations that cannot be completed in both systems. More robust interactions can use reliable asynchronous messaging (supported by Info*Engine JMS adapter) to reduce the dependency on remote networks and remote repositories all being up at once.
Federation isnt easy. As if deploying and managing a single Windchill system werent hard enough, federating with other systems adds another whole layer of hard work to design and implement. Aside from the technology to achieve it, there are still many hard decisions to make about the federated object model that is, what are the federated types and operations and federated applications that you are trying to build? What inter-system associations need to be persisted, and where? The good news is that once you decide what youre trying to build as a federation, Windchill and Info*Engine have the wherewithal to build it.
Index of topics 1. 2. 3. 4. 5. 6. Trail File Compliance notes Memory Management notes Performance of CachedObjectReferences Object Initialization Rules Thread leakage XP Service Pack 2 - download problem
documents. One has the basics for creating and running trail scripts using browser_test - How to Create and Run Trail Files (for developers). The other has some guidelines for making sure pages are trail compliant - How To Make Pages Trail Compliant (for 8.0 clients). Additional information will be added to these How To docs as time permits such as DOM dump info for trail scripts and DCA info for trail compliance.
If the object is not to be modified and you plan to get some other persisted attribute like Description you can use: someCachedObjectReference.getReadOnlyObject() to get a direct cached version that avoids a clone and/or refresh of the Persistable Be careful not to then do aReadOnlyObject.get<blah>referenece().getObject() because the read-only version will now have in memory a second persistable of <blah> type that could become stale. If you do this, follow it up with a aReadOnlyObject.get<blah>referenece().deflate() to remove the second <blah> persistable from memory (well, allow it to happen through GC). Putting a Persistable into a cache Problem: Creating a large project added over 60megs of memory used by the method Server. Why: The 600kb project template xml file was inflated on a reference to over 100 other cached persistables. How to Avoid: (From John G.) ..." services adding to their cache through creation APIs, they can (and should) do the opposite, which is to make a clone (wt.pds.PersistenceCloner) of the object to keep it independent of the one returned to the creating transaction. Makeing persistence clones strips all non-persistent data from the object, thus implicitly deflating all object references inside the object." This also makes the server run faster because each time the object is serialized or cloned (to other method servers or from cache), these extra persistable will no loner also be serialized or cloned. Serializing and cloning 600kb of xml takes some time.
associated with parts has been reduced. Instead, the practice of using promotion requests and change notices to promote a set of parts and CAD documents would be the preferred practice. The promotion request has been set up to offer greater flexibility in terms of the user's choice of promotion process (which could have a variety of different teams defined) and provides the ability to adjust the role assignments prior to invoking the promotion process. So, participation in the promotion process can be highly variable. The change process, however, is still rather rigid, but tailorable by site per the OOTB workflow process definition tools. A fall out of some of the X-05 work is that the initialization rules will be re-evaluated when the revise operation is performed. This means that parts and CAD docs that reside in the various containers will pick up the "latest" lifecycle models as new revisions are made. This not only supports a lazy migration from "Heavy (or Advanced) Lifecycles" to "Lightweight (or Basic) Lifecycles" for performance benefit, but enables a lazy migration from simple to more complex maturity models as well.
Note that with Windchill PDM, the system displays a revise dialog that gives the user the choice to reassign both the team and the lifecycle as part of the revise operation. PDMLink chose not to support a revise dialog so the system adheres completely to the object initialization rules. However, the flexibility has been significantly enhanced per the items I noted above.
http://www-900.ibm.com/developerWorks/cn/java/j-jtp0924/index_eng.shtml
The article is about thread leakage, but more generically it's about exception (Throwable) handling in general. The two main issues are the handling of unchecked exceptions (RuntimeExceptions and Error subclasses of java.lang.Throwable), and server reliability when critical threads terminate, possibly due to such exceptions. A server is responsible for both synchronous (call/return) and asynchronous (background) processing, and I'll answer for them separately since the issue of affects them differently. The responsibility of synchronous processing is to log, and if possible return, unchecked exceptions to the caller and guarantee that the network service such as RMI or SOAP listeners remain available for incoming requests. Windchill processing of requests makes a best effort to catch, log, and return to caller all Throwables (Exceptions, RuntimeExceptions, and Errors) raised by dispatched code. The actual code responsible for this depends on the network listener in question, and the next step would be a code review if you want to confirm that our implementations are aware of the differences between Exception, RuntimeException and Error Throwables. The threads performing synchronous operations depend on the network listener servicing the request, but as an example, in the case of RMI requests, the listening thread is controlled by Sun RMI code in the JRE, and it services connected sockets in separate _________________________________________________________________________ Volume 1, Number 3 - Fall 2004
threads. Our use of RMI allows our server code to log and package both checked and unchecked exceptions for return to the caller. If any of these threads abort nonetheless, catch blocks guarantee that the socket connection is closed so that client will not be left hanging. Similar story for other request transports. Asynchronous processing has the same reporting responsibility (i.e. no silent failures), but is also responsible for guaranteeing that background processes don't grind to a halt because of one failure. Windchill background processing achieves reliability NOT by relying on inmemory state and thread pools, but instead by being implemented as processing queues with persistent entries stored in the Windchill Oracle database. Atomic database transactions are an important part Windchill processing. In order to provide guaranteed execution of asynchronous operations that are triggered by the commitment of a transaction, the asynchronous operation is itself recorded persistently within the same transaction. Background threads then service these queues, and only when an entry processes to completion is the entry removed from the queue as part of the transaction processing the entry. This not only protects from thread leakage as outlined in the given article, but also protects against ungraceful JVM death (kill -9 or core dump) and system failures (hardware and software crashes), which no amount of expert Java programming can prevent or work around on its own, but without which a server cannot claim to be providing "reliable" asynchronous processing. An added benefit is that failed queue entries can be skipped but remain available for corrective action to be taken. For example, mail queued to be sent to a failed mail server can be resent when the mail server is available rather than just logging failure messages. The threads performing background processing in Windchill servers are started by one of our internal services called the queue service. Different Windchill server processes may be given responsibility for different queues or none at all, but when an instance of Windchill's queue service inside a server finds itself responsible for execution of entries from one or more queues, the queue service starts one thread that is responsible for starting and monitoring other threads dedicated to each queue. These queue processing threads are written so that failures are logged, and if the database is still accessible, the status of the persistent queue entry is updated in the database. On server startup, queue entries that were marked as being in an EXECUTING state are reverted back to a READY state since that would indicate that the system failed (killed or system crash/reboot) before their execution transactions were committed.
Note: The above steps should be performed even if you've already added the Windchill server URL to the popup blocker. SP2's restrictions reach beyond the popup blocking.
1. SPITWAD Back Issues Back issues in Windmill 2. Beyond Compare tool. This tool is available via CDE and provides Windows users excellent capabilities to compare files, as well as whole directory structures and archives. It can print reports for a code review highlighting the added, removed and changed lines, including context. Look for a future Development Services Tip on this tool. 3. Deveopment Services Tips highlighting internal tools 4. WhereUsed tool - provides a fast and accurate option for analyzing API behavior before changing or removing an API or method. 5. Windchill Javadoc - internal & external Javadoc for past and present Windchill releases 6. UI Standards Page user interface standards in draft or approved status 7. Modularization (M12N) Page modularization and ANT framework info 8. PTC Terminology Resource Center - information to help improve the usability of our products 9. Windchill Package Owner List - wnc.x-05 stream. This allows anyone to quickly correct ownership assignments for new or existing packages, and confirms the change with email notification to both past and present owners. 10. Windchill Architecture Team home page including a discussion forum for threaded discussions on a variety of architecture topics. 11. R&D Photo Album Get to know your neighbors.
Development Services Tool Overview (December) Performance Profiling Using OptimizeIt (December pending resource availability) Performance Overview / Coding for Performance Security Access Control ProjectLink 7.0 for X-05 EPM Performance 508 Accessibility
This list will grow as we evaluate the new hire training gaps and try to fill those gaps. Presented since June 2004 (through November 15, 2004)
Build on the Desktop D.Weaver (recording; PowerPoint) Windchill Aerospace & Defense Module (WADM) Various GSO presenters Day 1 presentations (recording) Day 2 presentations (recording) Day 3 presentations (recording) User Guide Overview (recording) Automated Test Training K. Majeti (PowerPoints for all sessions) UI Trail Automation Overview (recording) UI Trail Test Planning and Use of ARTS client (recording) UI Trail Test Failures & Troubleshooting (recording) Sun Technology: Overview of Java 1.5 Tiger Sun personnel (recording) Development Services Shared Lab J.Grano, K.Vokhrina (recording; PowerPoint) Gorilla Mode Training D.Coffey (recording; PowerPoint) Performance Profiling Tool J.Kirloskar (recording; PowerPoint) Lifecycle & Workflow Overview J.Quevedo, A.Wang (recording, Lifecycle PowerPoint, Workflow PowerPoint) SEP Overview J.Wachutka (recording, PowerPoint)
Install Essentials Install Team (recording, PowerPoint) PDMLink Customization & Lessons Learned for DaimlerChrysler S.Dertien (recording) Multi-Object API Symposium multiple presenters (recording, PowerPoint) High Level Estimating R.Kelley, P.McKenney L.Wiggert (recording, PowerPoint, SOP, Estimation Spreadsheet) X-10/Raytheon Common Designs & Initial/Functional Specs Kim Moronis team (recording, PowerPoint, SOP, Common Design document)
2. The ACM Greater Boston Web Tech Chapter is a special interest group (SIG)
Upcoming Efforts
There are two major emphases in training at this time: Process Training: Thus far we have given the SEP Overview presentation in Arden Hills, Needham and Pune. The recording is also now available. We have also given courses recently on High Level Estimates and Raytheon/X-10 Common Designs and Initial Specs. Stay tuned for more classes in the coming weeks as we roll out training in a number of major process areas, with emphasis on that training which is relevant to the SEP phase we are in or about to begin. New Hire Training: Efforts are underway to present a series of training sessions to the new hires. Some will be live sessions, others group replays of recordings, with opportunity for Q&A with a subject matter expert. We are also trying to fill the gaps in our training and to update materials that are out of date. Stay tuned for more information. In addition, the following are being actively worked: Windchill Skills Assessment: A focus group has been formed to work on this assessment over the upcoming weeks. The plan is to have a number of teams/functional areas provide their top 5 or 10 items and form those into a series of role based questionnaires, with links to content people can view about those questions if they dont know the answers. This will not be a formal certification, but more of a self-check to see if we know and understand critical information. FastStart Plans in PTCU: We are to migrate common information from the Microsoft Word based FastStart plans currently in use to learning paths in PTCU. There would be a group of courses/links for company wide resources, and another set for common Windchill resources. This way we can use the power of PTCU, but managers or mentors would only need to sign new hires up for the proper learning path, not a lot of individual courses. A couple of issues still exist, but we hope to have at least the broad levels in place within the next 30 days. Some helpful resources for you include: Recorded Training Resources link on Windchillweb (soon to be totally redone in a more role-based fashion) Group Leader courses you may find helpful (Windmill Document #1452666). Technical Courses you may find helpful, with detailed descriptions for those that seemed most applicable to Windchill developers (Windmill Document #1452660) PTC University entry page
To Editor; Where did the term Gorilla come from and who authorized its use? I may file a class-action suit on behalf of a species. Marlin Perkins [Ed. note: Uffda, get in line. Gorilla is actually an affectionate term. From the Windchill folklore album - way back when Windchill consisted of 15 souls - it was actually possible for one person to comprehend and monitor the entire