cancel
Showing results for 
Search instead for 
Did you mean: 
cancel
Showing results for 
Search instead for 
Did you mean: 

Community Tip - Need to share some code when posting a question or reply? Make sure to use the "Insert code sample" menu option. Learn more! X

IoT Tips

Sort by:
1. Create a network and added all Entities that implement from a specific ThingShape in the network 2. Create a ThingShape mashup as below Note: Bind the Entity parameter to DynamicThingShapes_TracotrShape's service GetProperties input EntityName. Laso bind mashup RefreshRequested event to that service 3. Create a mashup named ContentShape, add Tree widget and ContainedMashp in it 4. Bind Service GetNetworkConnection's Selected Row(s) result and Selected RowsChanged event to ContainedMashup widget Note: Master can total replace ThingShape mashup. Suggest to use Master after ThingWorx 6.0
View full tip
Embedded databases come with the installation of the ThingWorx Platform No additional installation or configuration is required for embedded databases Read about the various benefits and pitfalls of embedded versus external below Database Options H2 RDBMS (relational database management system), written in Java Has a small memory footprint Embedded into ThingWorx for easy installation Not as robust as other database options Not scalable in production environments (unless used alongside a separate, external database for stream, value stream, and other data) ​ See KCS Article CS243975 for further reading on the use of external databases Meant to be used for quick deployments and testing environments PostgreSQL ORDBMS (object-relational database management system), written in C PostgreSQL is the ThingWorx recommended database for production systems More Robust External database installed separately from ThingWorx Beneficial because external databases can be specifically configured for use in production, while embedded databases cannot Able to efficiently handle larger amounts of data and store more data without affecting ThingWorx system performance Greater Stability Recover from data corruptions more easily by accessing the database from an external application (separate from ThingWorx) using simple SQL statements Easier to back-up the database in case of issues (further reading in KCS Article CS246598) Less risky and simpler upgrade procedure, which occurs "in-place" Instead of exporting and importing data and entities, a simple schema update allows these to automatically persist into the new version If ThingworxStorage folder is accidentally deleted, entities and data are secure in the external database More Secure HA (High Availability) allows for multiple server instances at different locations in the network Assists in time of failover, i.e. if one server fails, the other can immediately take over Secures the data and prevents further data loss in the event of a failure Customizable security settings and complex password requirements Fewer security vulnerabilities than other databases Because Postgres is an external database, it can be harder to install Follow the steps in the installation guide closely See KCS Articles CS235937 and CS230085 for troubleshooting and help with installation and configuration Hana RDBMS (relational database management system) In-memory, column based data storage For more information on this database, please see the Getting Started with SAP HANA Guide Neo4J GDBMS (graph database management system), written in Java Data is not easily accessed by external applications, and CQL must be used instead of SQL, making recovery from corruptions very difficult Embedded database with limited configuration options Known to have issues with deadlocks Deprecated in version 7.0 (related KCS Article: CS228537) For full installation steps for H2 and PostgreSQL, see the ThingWorx Installation Guide
View full tip
I had a better example, this is my original example ... If I find the other one I'll upload that as well.
View full tip
This document is a general reference/help with configuring and troubleshooting google email account with the ThingWorx mail extension. To start with the configuration: SMTP: smtp.gmail.com 587, TLS checked.  If SSL is being used, the port should be 465. POP3: pop.gmail.com 995 To test, go to "Services" and click on "test" for the SendMessage service. Successful request will show an empty screen with green "result" at the top. Possible errors: Could not connect to SMTP host: smtp.gmail.com, port: 587 with nothing else in the logs. Check your Internet connection to ensure it's not being blocked. <hostname:port>/Thingworx/Common/locales/en-US/translation-login.json 404 (Not Found) Check your gmail folders for incoming messages regarding a sign-in from unknown device. The subject will be "Someone has your password", and the email  content will include the device, location, and timestamp of when the incident occurred. Ensure to check the "this was me" option to prevent from further blocking. This may or may not be sufficient, sometimes this leads to another error - "Please log in via your web browser and 534-5.7.14 then try again. 534-5.7.14 Learn more at 534 5.7.14..." The error can be resolved by: Turning off “less secure”  feature in your Gmail settings. You have to be logged in to your gmail account to follow the link: https://www.google.com/settings/security/lesssecureapps​ Changing your gmail password afterwards. I don't have a valid explanation as to why, but this is a required step, and the error doesn't clear without changing the password.
View full tip
In this video we cover: a short introduction of Thingworx Analytics Builder The import of the Thingworx Analytics Builder extension   This video applies to ThingWorx Analytics 52.1 till 8.1   Updated Link for access to this video:  Installing Thingworx Analytics Builder:  Part 1 of 3
View full tip
  Part I – Securing connection from remote device to Thingworx platform The goal of this first part is to setup a certificate authority (CA) and sign the certificates to authenticate MQTT clients. At the end of this first part the MQTT broker will only accept clients with a valid certificate. A note on terminology: TLS (Transport Layer Security) is the new name for SSL (Secure Sockets Layer).  Requirements The certificates will be generated with openssl (check if already installed by your distribution). Demonstrations will be done with the open source MQTT broker, mosquitto. To install, use the apt-get command: $ sudo apt-get install mosquitto $ sudo apt-get install mosquitto-clients Procedure NOTE: This procedure assumes all the steps will be performed on the same system. 1. Setup a protected workspace Warning: the keys for the certificates are not protected with a password. Create and use a directory that does not grant access to other users. $ mkdir myCA $ chmod 700 myCA $ cd myCA 2. Setup a CA and generate the server certificates Download and run the generate-CA.sh script to create the certificate authority (CA) files, generate server certificates and use the CA to sign the certificates. NOTE: Open the script to customize it at your convenience. $ wget https://github.com/owntracks/tools/raw/master/TLS/generate-CA.sh . $ bash ./generate-CA.sh The script produces six files: ca.crt, ca.key, ca.srl, myhost.crt,  myhost.csr,  and myhost.key. There are: certificates (.crt), keys (.key), a request (.csr a serial number record file (.slr) used in the signing process. Note that the myhost files will have different names on your system (ubuntu in my case) Three of them get copied to the /etc/mosquitto/ directories: $ sudo cp ca.crt /etc/mosquitto/ca_certificates/ $ sudo cp myhost.crt myhost.key /etc/mosquitto/certs/ They are referenced in the /etc/mosquitto/mosquitto.conf file like this: After copying the files and modifying the mosquitto.conf file, restart the server: $ sudo service mosquitto restart 3. Checkpoint To validate the setup at this point, use mosquitto_sub client: If not already installed please install it: Change folder to ca_certificates and run the command : The topics are updated every 10 seconds. If debugging is needed you can add the -d flag to mosquitto_sub and/or look at /var/logs/mosquitto/mosquitto.log. 4. Generate client certificates The following openssl commands would create the certificates: $ openssl genrsa -out client.key 2048 $ openssl req -new -out client.csr  -key client.key -subj "/CN=client/O=example.com" $ openssl x509 -req -in client.csr -CA ca.crt  -CAkey ca.key -CAserial ./ca.srl -out client.crt  -days 3650 -addtrust clientAuth The argument -addtrust clientAuth makes the resulting signed certificate suitable for use with a client. 5. Reconfigure Change the mosquitto configuration file To add the require_certificate line to the end of the /etc/mosquitto/mosquitto.conf file so that it looks like this: Restart the server: $ sudo service mosquitto restart 6. Test The mosquitto_sub command we used above now fails: Adding the --cert and --key arguments satisfies the server: $ mosquitto_sub -t \$SYS/broker/bytes/\# -v --cafile ca.crt --cert client.crt --key client.key To be able to obtain the corresponding certificates and key for my server (named ubuntu), use the following syntax: And run the following command: Conclusion This first part permit to establish a secure connection from a remote thing to the MQTT broker. In the next part we will restrict this connection to TLS 1.2 clients only and allow the websocket connection.
View full tip
This is a reference document on how to move existing or fresh install ThingworxStorage location. Note: As of Thingworx 7.0 the platform-settings.json file has changed and therefore the newest version needs to be used  to change the location of the folders. Please refer to this guide pg.45-46 to see the new .json file.http://support.ptc.com/WCMS/files/170230/en/Installing_ThingWorx_7.1.pdf  A sample json is attached at the bottom of this document. Here are the main bullet points that need to be kept in mind when performing a migration process: ThingworxPlatform directory has to be in the same partition as where the Tomcat is installed. I.e. if your tomcat is in /apps, the ThingworxPlatform should also be in /apps, not in root. Note: if ThingworxPlatform will be placed in a different partition than the tomcat, create a THINGWORX_PLATFORM_SETTINGS environmental variable that will point to the correct location. Refer to the Installation Guide for details. Before modifying the location of the platform-settings.json, stop tomcat. There is no need to undeploy the Thingworx app, but do rename the existing folders (as a way of copying/preserving data if any exists) “ThingworxStorage” to “ThingworxStorageCopy” and “ThingworxBackupStorage” to “ThingworxBackupStorageCopy”. If this is a fresh install, proceed to step 3.. Modify the platform-settings.json to have desired locations for the ThingworxStorage folders. Start the tomcat. New ThingworxStorage and ThingworxBackupStorage will be created in the new location. Note: if there is any data in the ThingworxStorage (see ThingworxStorageCopy and ThingworxBackupStorageCopy from step 2), stop the tomcat and place the contents of the original folders into the new ones or just replace the directories. For Postgresql  scenarios: Ensure postgresql database is running. If fresh install/no need to preserve data,  and in case of 401 error, clean the existing database via the script and run the two scripts to install and setup DB and Schema. Start tomcat and verify connection. If data does need to be preserved, then run the schema update script to update from 6.5 to 6.6. then to 7.0 and 7.1 respectively, depending on which version you are starting with. To change the location of where postgresql would write data, find the thingworxPostgresDBSetup.sh script open it to edit: tablespace_location="/ThingworxPostgresqlStorage" Then re-run the DBsetup and Schema setup while tomcat is stopped. For assistance in resolving any difficulties related to the migration process, please contact Thingworx Technical Support.
View full tip
There's a reason why many "Hello World" tutorials begin with writing to the logging tool.  Developers live in the console, inserting breakpoints and watching variables while debugging.  Especially with interconnected, complex systems, logging becomes crucial to saving developer hours and shipping on time. What this tutorial covers This tutorial introduces the three principal methods of monitoring the status of assets and the output of operations on an Axeda instance. Audit Logs Custom Object Log Reports The Audit Log You can filter the Audit Log by date range or by category. A list of the Audit Categories is available in the Help documentation at http://<<yourhost>>.axeda.com/help/en/audit_log_categories_for_filtering... You can write to the Audit Log from an Expression Rule or from a Custom Object. Writing to the Audit Log from an Expression Rule Use the Audit Action to write to the Audit Log: Audit("data-management", "The temperature " + DataItem.temp.value + "was reported at " + FormatDate(Now(), "yyyy/MM/dd hh:mm:ss")) You can insert values from the Variables or Functions list by using the plus operator as a string concatenator. Writing to the Audit Log from a Custom Object import com.axeda.common.sdk.id.Identifier import com.axeda.drm.sdk.Context import com.axeda.drm.sdk.audit.AuditCategory import com.axeda.drm.sdk.audit.AuditMessage auditMessage(Context.getSDKContext(), "data_management", "Thread started timestamp has ended.", context.device.id) private def auditMessage(Context CONTEXT, String category, String message, Identifier assetId) {     AuditCategory auditCategory = AuditCategory.getByString(category) ?: AuditCategory.DATA_MANAGEMENT     if (assetId == null) {         new AuditMessage(CONTEXT, "com.axeda.drm.rules.functions.AuditLogAction", auditCategory, [message]).store()     } else {         new AuditMessage(CONTEXT, "com.axeda.drm.rules.functions.AuditLogAction",    auditCategory, [message], assetId).store()     } }     In either case, a message written in the context of an Asset will be displayed on the Asset Dashboard (assuming the Audit module is enabled for the Asset Dashboard in the Model Preferences). The Custom Object Log The Configuration (6.1-6.5)/Manage(6.6+) tab provides access to the Custom Objects log when they are selected from the View sub-menu: This links allows you to open or save a zip archive of text files called customobject.logX where X is a digit that indicates that the log has rolled over into the next file (ie, customobject.log1).  The most current is customobject.log without a digit.  These files contain logging information in chronological order, identified by Custom Object name.  The log contains full stack traces of exceptions, as well as text written to the log. ERROR 2013-06-20 18:26:02,613 [sstreBinaryReturn,ajp-10.16.70.164-8009-6] Exception occurred in sstreBinaryReturn.groovy: java.lang.NullPointerException     at com.axeda.platform.sdk.v1.services.extobject.ExtendedObject.getPropertyByName(ExtendedObject.java:276)     at com.axeda.platform.sdk.v1.services.extobject.ExtendedObject$getPropertyByName.call(Unknown Source)    The Logger object in Custom Objects is a custom class ScriptoDebuggerLogger that is injected into the script and does not need to be explicitly imported. The following attributes are available for the Logger object: logger.info() logger.debug() logger.error()   All objects can be converted to a String by using the dump() function. logger.info(context.device.dump()) Additionally, a Javascript utility can be used with all SDK v2 domain objects and some SDK v1 domain objects to get a JSON pretty-print string of their attributes. import net.sf.json.JSONArray logger.info(JSONArray.fromObject(context.device).toString(2)) // Outputs: [{   "buildVersion": "",   "condition":  {     "detail": "",     "id": "3",     "label": "",     "restUrl": "",     "systemId": "3"   },   "customer":  {     "detail": "",     "id": "2",     "label": "Default Organization",     "restUrl": "",     "systemId": "2"   },   "dateRegistered":  {     "date": 31,     "day": 4,     "hours": 18,     "minutes": 39,     "month": 0,     "seconds": 31,     "time": 1359657571070,     "timezoneOffset": 0,     "year": 113   },   "description": "",   "detail": "mwc_location_1",   "details": null,   "gateways": [],   "id": "12345",   "label": "",   "location":  {     "detail": "Default Organization",     "id": "2",     "label": "Default Location",     "restUrl": "",     "systemId": "2"   },   "model":  {     "detail": "mwc_location",     "id": "4321",     "label": "standalone",     "restUrl": "",     "systemId": "4321"   },   "name": "mwc_location_1",   "pingRate": 10000,   "properties": [],   "restUrl": "",   "serialNumber": "mwc_location_1",   "sharedKey": [],   "systemId": "12345",   "timeZone": "America/New_York" }] ​Custom object logs may be retrieved by navigating to the Configuration (6.1-6.5)/Manage(6.6+) tab and selecting Custom Objects from the View sub-menu. Click the "Log" button at the bottom of the table and save, then view customobject.log in a text editor. Reports Reports provide a summary of data about the state of objects on the Axeda Platform.  Report titles are generally indicative of what they're reporting, such as Missing Devices Report, Auditing Report, Users Report. A separate license is needed in order to use the Reports feature.  New report types can only be created by a Reports administrator. To run a report, click Run from the Reports Tab. You can manage Reports from the Administration tab. These three tools together offer a full view of the state of domain objects on the Axeda Platform.  Make sure to take advantage of them while troubleshooting assets and applications.
View full tip
The following script is a component of the Axeda Connected Configuration (CMDB) feature.  It is used to provide configuration data for controlling package deployments via Connected Content (SCM). ​ ConfigItem_CRU.groovy *Takes a POST request, not parameters import static com.axeda.sdk.v2.dsl.Bridges.* import com.axeda.drm.sdk.scripto.Request import com.axeda.services.v2.ConfigurationItem import com.axeda.services.v2.ConfigurationItemCriteria import com.axeda.services.v2.AssetConfiguration import com.axeda.services.v2.Asset import com.axeda.services.v2.ExecutionResult import groovy.json.JsonSlurper import net.sf.json.JSONObject import groovy.xml.MarkupBuilder /** * ConfigItem_CRU.groovy * ----------------------- * * Reads in json from an http post request and reads, adds, deletes or updates Configuration Items. * * * @note this parses a post and does not take any additional parameters. * * @author sara streeter <sstreeter@axeda.com> */ def contentType = "application/json" final def serviceName = "ConfigItem_CRU" def response = [:] def writer = new StringWriter() def xml = new MarkupBuilder(writer) try {     // BUSINESS LOGIC BEGIN     def assetId     def validationOnly     def validationResponse = ""     List<ConfigurationItem> configItemList     if (Request?.body != null && Request?.body !="") {         def slurper = new JsonSlurper()         def request = slurper.parseText(Request?.body)         assetId = request.result.assetId         validationOnly = request.result.validationOnly?.toBoolean()         if (request.result.items != null && request.result.items.size() > 0){             configItemList = request.result.items.inject([]) { target, item ->               if (item && item.path != "" && item.key != "" && item.path != null && item.key != null){                     ConfigurationItem configItem = new ConfigurationItem()                     configItem.path = item.path + item.key                     configItem.value = item.value                     target << configItem                 }                 target             }         }     }       if (assetId != null) {               def asset = assetBridge.find([assetId])[0]             AssetConfiguration config = assetConfigurationBridge.getAssetConfiguration(assetId, "")               def itemToDelete                        if (config == null) {                     createConfigXML(xml)                     AssetConfiguration configToCreate = assetConfigurationBridge.fromXml(writer.toString(), asset.id)                     ExecutionResult result = assetConfigurationBridge.create(configToCreate)                     AssetConfiguration config2 = assetConfigurationBridge.getAssetConfiguration(asset.id, "")                     config = config2                     itemToDelete = "/Item"                 }                 if (configItemList != null && configItemList?.size() > 0){                 List<ConfigurationItem> compareList = config.items                 def intersectingCompareItems = compareList.inject(["save": [], "delete": []]) { map, item ->                     // find whether to delete                     def foundItem = configItemList.findAll{ compare -> item?.path == compare?.path && item?.value == compare?.value  }                     map[foundItem.size() > 0 ? "save" : "delete"] << item                     map                 }               intersectingCompareItems.delete = intersectingCompareItems.delete.collect{it.path}               if (itemToDelete){                 intersectingCompareItems.delete.add(itemToDelete)               }                 def intersectingConfigItems = configItemList.inject(["old": [], "new": []]) { map, item ->                     // find whether it's old                     def foundItem = compareList.findAll{ compare -> item?.path == compare?.path && item?.value == compare?.value }                     map[foundItem.size() > 0 ? "old" : "new"] << item                     map                 }                 assetConfigurationBridge.deleteConfigurationItems(config, intersectingCompareItems.delete)                 assetConfigurationBridge.appendConfigurationItems(config, intersectingConfigItems.new)               def exResult = assetConfigurationBridge.validate(config)               if (exResult.successful){                     validationResponse = "success"                     if (!validationOnly){                         assetConfigurationBridge.update(config)                     }               }                 else {                     validationResponse = exResult.failures[0]?.details                 }             }             response = [                 assetId: assetId,                 items: config?.items?.collect { item ->                 def origpath = item.path                 def lastSlash = origpath.lastIndexOf("/")                 def key = origpath.substring(lastSlash + 1, origpath.length())                        def path = origpath.replace("/" + key, "")                 path += "/"                     [                         path: path,                         key: key,                         value: item.value                     ]                 },                 validationResponse: validationResponse             ]       }         else {             throw new Exception("Error: Asset Id must be provided.")         } } catch (Exception ex) {       logger.error ex   response = [           error:  [                   type: "Backend Application Error"                   , msg: ex.getLocalizedMessage()           ]   ] } return ['Content-Type': 'application/json', 'Content': JSONObject.fromObject(response).toString(2)] /** * Create the Success response. * * @param xml : The xml response.<br> * @param info : If this is set to "1" the info element will be included in the response.<br> * @param infos : Collection of information to include within the info element of the response.<br> */ private void createConfigXML(xml) {     xml.Item() }  
View full tip
I recently had a customer who wanted to run services on ThingWorx from Power BI to retrieve existing operational data, and we were a bit stumped on how to pass the API key over in the headers, so I did a bit of Googling and pieced together the solution. It's not quite intuitive on the Power BI side, so I thought it would be helpful to share. If you have any other experience with integrating ThingWorx with Power BI, feel free to add a comment.    Prepare ThingWorx Create an Application Key that has Run Time execution access to the services you need. Understand the inputs needed for the service you would like. I'll have examples of none, one, an InfoTable, and multiple inputs.   Power BI Following the following steps in Power BI: 1. In Power BI, create a new blank query   2. On the left, right click on Query1 and go to the Advanced Editor:   3. Replace all of the body content with the following, replacing your API key, appropriate end point, and base URL as needed (this is an example with NO input parameters, I'll follow with examples of other parameters):     let appKey = "your-application-key-here", endpoint = "Things/YourThingNameHere/Services/YourServiceNameHere", baseUrl = "https://YourServerNameHere/Thingworx/", url = Text.Combine({baseUrl,endpoint}), body = "", request = Web.Contents( url, [ Headers = [ appKey = appKey, #"Content-Type" = "application/json", Accept = "application/json" ], Content = Text.ToBinary(body) ] ), Source = Json.Document(request) in Source       4. Click "Done", and now you'll have a warning about how to connect. Click the "Edit Credentials" button. 5. Leave it on Anonymous and click "Connect":   6. You should now see the return data coming from ThingWorx.   Note that I had a little trouble with this authentication initially and it saved the wrong method. To clear that out, go to the ribbon bar item "Data source settings" and select the server and clear it out.   Other Examples Here is an example for sending a single string parameter:   let appKey = "your-application-key-here", endpoint = "Things/YourThingNameHere/Services/YourServiceNameHere", baseUrl = "https://YourServerNameHere/Thingworx/", url = Text.Combine({baseUrl,endpoint}), body = "{""InputParameter"": ""InputValue""}", request = Web.Contents( url, [ Headers = [ appKey = appKey, #"Content-Type" = "application/json", Accept = "application/json" ], Content = Text.ToBinary(body) ] ), Source = Json.Document(request) in Source     Here's an example of sending a string and an integer: let appKey = "your-application-key-here", endpoint = "Things/YourThingNameHere/Services/YourServiceNameHere", baseUrl = "https://YourServerNameHere/Thingworx/", url = Text.Combine({baseUrl,endpoint}), body = "{""InputString"": ""Hello, world!"", ""InputNumber"" : 42}", request = Web.Contents( url, [ Headers = [ appKey = appKey, #"Content-Type" = "application/json", Accept = "application/json" ], Content = Text.ToBinary(body) ] ), Source = Json.Document(request) in Source   Here is an example for sending an InfoTable. Note that you must supply the dataShape with fieldDefinitions. If you're using an existing Data Shape, you can get the JSON by using the service GetDataShapeMetadataAsJSON() that is on the data shape.     let appKey = "your-application-key-here", endpoint = "Things/YourThingNameHere/Services/YourServiceNameHere", baseUrl = "https://YourServerNameHere/Thingworx/", url = Text.Combine({baseUrl,endpoint}), body = "{""propertyNames"": { ""rows"": [ { ""name"": ""FirstEntityName"", ""description"": ""The first entity"" }, { ""name"": ""SecondEntityName"", ""description"": ""The second entity"" }], ""dataShape"": { ""fieldDefinitions"": { ""name"": { ""name"": ""name"", ""aspects"": { ""isPrimaryKey"": true }, ""description"": ""Entity name"", ""baseType"": ""STRING"", ""ordinal"": 0 }, ""description"": { ""name"": ""description"", ""aspects"": {}, ""description"": ""Entity description"", ""baseType"": ""STRING"", ""ordinal"": 0 } } } }}", request = Web.Contents( url, [ Headers = [ appKey = appKey, #"Content-Type" = "application/json", Accept = "application/json" ], Content = Text.ToBinary(body) ] ), Source = Json.Document(request) in Source       If I find any more interesting ways to use Power BI with ThingWorx services, I'll add them on here.  
View full tip
Background Getting a performance benchmark of your running application is an important thing to do when deploying and scaling up an application in production.  This not only helps focus in on performance issues quickly, but also allows for safely planning for scaling up and resource sizing based on real concrete data.   I recently created a tool and made a post about capturing and analysing ThingWorx utilisation statistics to do such an analysis, as well as identifying potential performance bottlenecks. Although they are rich and precise, utilisation statistics fall short in a number of areas however - specifically being able to count and time specific service executions, as well as identifying and sorting based on the host executing the service.   Tomcat Access Log Analysis As ThingWorx is a Tomcat web application, Tomcat logs details of the requests being made to the application server and ThingWorx REST API.  The default settings include the host (IP address), date/timestamp, and request URI; which can be decoded to reveal relevant details like the calling entities and service executions.   Adding 3 key additional variables (%s %B %D) to the server.xml access log value also gives us the HTTP response code, service execution time, and bytes returned from Tomcat.  This is super useful as we can now determine exact time of service executions, and run statistics on their execution totals and execution time.     Once you have an access log file looking like the one above, you can attempt to load it into the access_log sheet in the analysis Excel workbook that I created.  You do this by click on the access_log table, then selecting "Data > Get Data > Data Source Settings".  You'll then be prompted with the following or similar pop-up allowing you to navigate to your access_log file to select and then load.     It should be noted that you'll have to Refresh the table after selecting the new access_log.txt file so that it is read in and populates the table.  You can do this by right-clicking on the table and saying Refresh, or using the Data > Refresh button.   This workbook relies on a number of formulas to slice and dice the timestamp, and during my attempts at importing I had significant issues with this due to some of the ways that Excel does things automatically without any manual options.  You really need to make sure that the timestamps are imported and converted correctly, or something in the workbook will likely not work as intended.  One thing that I had to do was to add 1 second to round up 00:00:00 for the first entries as this was being imported as a date without the time part, and then the next lines imported as a date/time.   Depending on how many lines your file is, you'll likely also have to "Fill Down" the formulas on the right side of the sheet which may be empty in the table after importing your new data set.  I had the best results by selecting the cells in question on the last row, then going down to the bottom corner, pushing and holding Shift, clicking on the last cell bottom right, and then selecting Home > Fill > Down to pull the formulas down from the top.   Once the data is loaded, you'll be able to start poking around.  The filters and sorting by the named columns is really helpful as you can start out by doing things like removing a particular host, sorting by longest execution times, selecting execution times greater than 4 seconds, or only showing activity aimed at a particular entity or service.     You really need to make sure that the imported data worked fine and looks perfect, as the next steps will totally break if not.  With the data loaded, you can now go to the Summary Data table and right-click on one of the tables and select Refresh.  This is reload the data in into the pivot table and re-run their calculations.   Once the refresh is complete, you should see the table summary like shown here; there are Day, Hour, and Minute expand/collapse buttons.  You should also see the Day, Hour, Month fields showing in the Field Definitions on the right.  This is the part that is painful -- if the dates are in the wrong format and Excel is unable to auto-detect everything in the same way, then you will not get these automatically created fields.     With the data reloaded, and Pivot Tables re-built, you should be able to go over to the Dashboard sheet to start looking at and analysing the graphs.  This one is showing the Top 10 services organised into hourly buckets with cumulated service execution times.     I'm not going to go into all of the workbooks features, but you can also individually select a set of key services that you want to have a look at together across both the execution count and execution time dimensions.     Next you can see the coordinated view of both total service execution time over number or service executions.  This is helpful for looking for patterns where a service may be executing longer but being triggered the same amount of times, compared to both being executed and taking more time.  I've created a YouTube video (see bottom) which goes through using all of the features as well as providing other pointers to using it.     Getting into a finer level of detail, this "bonus" sheet provides a Pivot Table and Pivot Chart which allows for exploring minimum, maximum and average execution time for a specific service.  Comparing this with the utilisation subsystem metrics taken during the same period now provide much deeper insight as we can pinpoint there the peaks were, how long they lasted, and where the slow executions were in relation to other services being executed at that time (example: identifying many queries/data processing occurring simultaneously).     Without further ado, you can download and play with my ThingWorx Tomcat Access Log Analysis Excel Workbook, and check out the recorded demonstration and explanation for more details on loading and analysis use. [YouTube] ThingWorx Tomcat Access Logs - Service Performance Analysis
View full tip
As of Dec. 23, 2021, ThingWorx 9.3.0 is available for download and on Jan. 7, 2022, it will be available for Cloud Services.   What’s new in ThingWorx 9.3? Composer and Mashup Builder  Enhanced ability to find entities, code references, and project dependencies with a new Referenced by report feature. New Grid widget allows for improved visualization of row/columnar type data, with improved performance, styling, and configuration experiences.  Composer enhancements allow for faster configuration and application management in the areas of test execution and dynamic use of Master Mashups.    Analytics  New configuration parameters (binningStrategy and allowOverlap) available for profiles to tailor application of the algorithm to meet needs of domain use cases.  New option to utilize K-Fold cross validation to improve quality of predictive models created on limited data sets using industry standard technique.  Automate treatment of missing values to reduce data preparation before application of advanced analytics algorithms.    Foundation  Query performance improvements with indexing enables performance of quick “search” queries across thousands of connected assets and pre-filtering of query resultsets for better overall query performance.  Upgrade script improvements for logging, script execution and error handling which simplify the upgrade process.    Remote Access and Control  Improvements to Remote Access and Control includes Auto & Click to Launch capabilities that streamline the remote access startup process by starting the support application you need for you. New connectivity options enable user control and selection of local IP and Port addresses used in the session creation.    Software Content Management  Instructions can now be configured to execute either Synchronously or Asynchronously for a streamlined execution process. Added support for data migration of older, from the audit data table information into a format to the new audit database.    View release notes here and be sure to upgrade to 9.3!   Stay Connected, Rachel
View full tip
Requirements: 6.1.2+ Geofences are geometric shapes drawn virtually on a geographical area that represents a fence that can be crossed by a device.  The Axeda Platform has built-in support for mobile locations and geofences, which can be linked to the rules engine to enable notifications based on geofence crossing. What this tutorial covers This tutorial demonstrates the workflow of creating a geofence through to creating the expression rules with notifications, then how the mobile location can trigger the rules. 1) Creating the Geofence 2) Creating the Expression Rule There is currently no user interface built into the Axeda Applications Console which interacts with geofences.  For a sample application with a geofence user interface, see Sample Project: Traxeda​ (TODO).  For a single Custom Object that includes all of the functionality described below, see the end of  this document. The properties of a geofence are a name, a description, and a series of coordinates based on Well-Known Text (WKT) syntax (see the OpenGIS Simple Features Specification). def addGeofence(CONTEXT, map){     Geofence myGeofence = new Geofence(CONTEXT)        myGeofence.name = map.name     if(map.type != "polygon" && map.type != "circle")     {         throw new Exception("Invalid type: need 'polygon' or 'circle', not '$map.type'")     }     else if(map.type == "polygon")     {         def geo = map.locs.loc.inject( "POLYGON (("){ str, item ->             def lng = item.lng             def lat = item.lat             str += "$lng $lat,"         str         }         //the first location also has to be the last location         myGeofence.geometry = geo + map.locs.loc[0].lng + " " + map.locs.loc[0].lat + "))"         //Something like this is built:         //POLYGON ((-71.082118 42.383892,-70.867198 42.540923,-71.203654 42.495374,-71.284678 42.349394,-71.163829 42.221382,-71.003154 42.266114,-71.082118 42.383892))     }     else if(map.type == "circle")     {         def lng = map.locs.loc[0].lng         def lat = map.locs.loc[0].lat         myGeofence.geometry = "POINT ($lng $lat)"         //POINT (-71.082118 42.383892)         myGeofence.buffer = map.radius.toDouble()     }     myGeofence.description = "ALERT:::$map.alertType:::$map.alert"     try {          myGeofence.store()     }     catch (e){         logger.info e.localizedMessage             return null     }     myGeofence } The geofence itself does not interact with devices in any way.  Rather it is the Expression Rule that is applied to models and devices and that invokes the geofence when a mobile location is passed in. Creating the Expression Rule The Expression Rule for the Geofence is built as follows: TYPE: MobileLocation IF:  Expression set to "InNamedGeofence" for entering and "!InNamedGeofence" for exiting. The following function creates this expression rule: /* Sample call createGeofenceExpressionRule(CONTEXT, "My Geofence", "rule_MyGeofence", "in", "You entered the geofence!", "SDK Generated Geofence Rule", 100) */ def createGeofenceExpressionRule(com.axeda.drm.sdk.Context CONTEXT, String geofencename, String rulename, String alertType, String alertMessage, String ruledescription, int severity){     ExpressionRuleFinder erf = new ExpressionRuleFinder(CONTEXT)     erf.setName(rulename)     ExpressionRule expressionRule1 = erf.findOne()     expressionRule1?.delete()        def expressionRule = new ExpressionRule(CONTEXT)     expressionRule.setName(rulename)     expressionRule.setDescription(ruledescription)     expressionRule.setTriggerName("MobileLocation")     def ifExpStr = "InNamedGeofence(\"$geofencename\", Location.location)"     if(alertType == "out"){         ifExpStr = "!" + ifExpStr     }     expressionRule.setIfExpression(new Expression(ifExpStr))     expressionRule.setThenExpression(new Expression("CreateAlarm(\"$alertMessage\", severity)"))     expressionRule.setEnabled(true)     expressionRule.setConsecutive(false)     expressionRule.store()     expressionRule } Then the rule associations must be created to apply the rule to a model or device. /* Sample call findOrCreateRuleAssociations(CONTEXT, myModel, expressionRule, "EXPRESSION_RULE", "MODEL") Where expressionRule is the rule created in the above example */ def findOrCreateRuleAssociations(Context CONTEXT, Object entity, Object rule, String ruleType, String entityType){     // rule type is whether this is an expression rule     ruleType = ruleType ?: "EXPRESSION_RULE"     entityType = entityType ?: "DEVICE_INCLUDE"     RuleAssociationFinder ruleAssociationFinder = new RuleAssociationFinder(CONTEXT)     ruleAssociationFinder.setRuleId(rule.id.value)     ruleAssociationFinder.setRuleType(RuleType.valueOf(ruleType))     ruleAssociationFinder.setEntityId(entity.id.value)     ruleAssociationFinder.setEntityType(EntityType.valueOf(entityType))     def ruleAssociations = ruleAssociationFinder.findAll()     if (!ruleAssociations || ruleAssociations?.size() == 0){         def ruleAssociation = new RuleAssociation(CONTEXT)         ruleAssociation.entityId = entity.id.value         ruleAssociation.entityType = EntityType.valueOf(entityType)         ruleAssociation.ruleType = RuleType.valueOf(ruleType)         ruleAssociation.setRuleId(rule.id.value)         ruleAssociation.store()         ruleAssociations = [ruleAssociation]     }     return ruleAssociations } The rule will now be triggered when any device of the applied model sends a mobile location within the geofence, which in turn will create an alarm. Here is a custom object with the complete geofence functionality: import com.axeda.drm.sdk.Context import com.axeda.drm.sdk.geofence.Geofence import com.axeda.drm.sdk.geofence.GeofenceFinder import com.axeda.drm.sdk.rules.engine.Expression import com.axeda.drm.sdk.rules.engine.ExpressionRule import com.axeda.drm.sdk.rules.engine.ExpressionRuleFinder import com.axeda.drm.sdk.rules.engine.RuleAssociation import com.axeda.drm.sdk.rules.engine.RuleAssociationFinder import com.axeda.drm.sdk.rules.engine.RuleType import com.axeda.drm.sdk.common.EntityType import com.axeda.drm.sdk.device.Model import com.axeda.drm.sdk.device.ModelFinder try {     def Context CONTEXT = Context.getSDKContext()     def model = findOrCreateModel(CONTEXT, "FooModel")     def sampleCircle = [         "name": "My Circle",         "alert": "My Geofence Alert Text",         "type": "circle",         "alertType": "in",         "radius": "65.76",         "locs": [             [                 "loc": [   "lat": "42.60970621339408",   "lng": "-73.201904296875"   ]             ]         ]     ]     def samplePolygon = [         "name": "My Polygon",         "alert": "My Geofence Alert Text",         "type": "polygon",         "alertType": "out",         "locs": [             ["loc": [  "lng": -71.2604999542236,  "lat": 42.3384903145478  ]],             ["loc": [  "lng": -71.4218616485596,  "lat": 42.3242772020001  ]],             ["loc": [  "lng": -71.5585041046143,  "lat": 42.2653600946699  ]],             ["loc": [  "lng": -71.5413379669189,  "lat": 42.1885837119108  ]],             ["loc": [  "lng": -71.4719867706299,  "lat": 42.1137514551207  ]],             ["loc": [  "lng": -71.3737964630127,  "lat": 42.0398506628541  ]],             ["loc": [  "lng": -71.2508869171143,  "lat": 42.0311807962068  ]],             ["loc": [  "lng": -71.1355304718018,  "lat": 42.2084223174036  ]],             ["loc": [  "lng": -71.2604999542236,  "lat": 42.3384903145478  ]]         ]     ]     // find geofence if it exists     def circle = findGeofenceByName(CONTEXT, sampleCircle.name)     // create circular geofence     if (!circle){         circle = addGeofence(CONTEXT, sampleCircle)     }     // create rule for circular geofence     def circleRule = createGeofenceExpressionRule(CONTEXT, circle.name, "${circle.name}__Rule",                                                                            sampleCircle.alertType, sampleCircle.alert, "SDK Generated Geofence Rule", 100)     // apply rule to new Model     findOrCreateRuleAssociations(CONTEXT, model, circleRule, "EXPRESSION_RULE", "MODEL")     def polygon = findGeofenceByName(CONTEXT, samplePolygon.name)     if (!polygon){         polygon = addGeofence(CONTEXT, samplePolygon)     }     def polygonRule = createGeofenceExpressionRule(CONTEXT, polygon.name, "${polygon.name}__Rule",                                                                               samplePolygon.alertType, samplePolygon.alert, "SDK Generated Geofence Rule", 100)     // apply rule to new Model     findOrCreateRuleAssociations(CONTEXT, model, polygonRule, "EXPRESSION_RULE", "MODEL") } catch (Exception e) {     logger.info(e.localizedMessage) } return true def findGeofenceByName(CONTEXT, name){     GeofenceFinder geofenceFinder = new GeofenceFinder(CONTEXT)     geofenceFinder.setName(name)     def geofence = geofenceFinder.find()     geofence } def addGeofence(CONTEXT, map){     Geofence myGeofence = new Geofence(CONTEXT)     myGeofence.name = map.name     if(map.type != "polygon" && map.type != "circle") {         throw new Exception("Invalid type: need 'polygon' or 'circle', not '$map.type'")     } else if(map.type == "polygon") {         def geo = map.locs.loc.inject( "POLYGON (("){ str, item ->             def lng = item.lng             def lat = item.lat             str += "$lng $lat,"             str         }         //the first location also has to be the last location         myGeofence.geometry = geo + map.locs.loc[0].lng + " " + map.locs.loc[0].lat + "))"         //Something like this is built:         //POLYGON ((-71.082118 42.383892,-70.867198 42.540923,-71.203654 42.495374,-71.284678 42.349394,-71.163829 42.221382,-71.003154  42.266114,-71.082118 42.383892))     } else if(map.type == "circle") {         def lng = map.locs.loc[0].lng         def lat = map.locs.loc[0].lat         myGeofence.geometry = "POINT ($lng $lat)"         //POINT (-71.082118 42.383892)         myGeofence.buffer = map.radius.toDouble()     }     myGeofence.description = "ALERT:::$map.alertType:::$map.alert"     try {         myGeofence.store()     }  catch (e) {         logger.info e.localizedMessage         return null     }     myGeofence } def createGeofenceExpressionRule(com.axeda.drm.sdk.Context CONTEXT, String geofencename, String rulename,                                                      String alertType, String alertMessage, String ruledescription, int severity) {     ExpressionRuleFinder erf = new ExpressionRuleFinder(CONTEXT)     erf.setName(rulename)     ExpressionRule expressionRule1 = erf.findOne()     expressionRule1?.delete()     def expressionRule = new ExpressionRule(CONTEXT)     expressionRule.setName(rulename)     expressionRule.setDescription(ruledescription)     expressionRule.setTriggerName("MobileLocation")     def ifExpStr = "InNamedGeofence(\"$geofencename\", Location.location)"     if(alertType == "out"){         ifExpStr = "!" + ifExpStr     }     expressionRule.setIfExpression(new Expression(ifExpStr))     expressionRule.setThenExpression(new Expression("CreateAlarm(\"$alertMessage\", severity)"))     expressionRule.setEnabled(true)     expressionRule.setConsecutive(false)     expressionRule.store()     expressionRule } def findOrCreateRuleAssociations(Context CONTEXT, Object entity, Object rule, String ruleType, String entityType) {     // rule type is whether this is an expression rule     ruleType = ruleType ?: "EXPRESSION_RULE"     entityType = entityType ?: "DEVICE_INCLUDE"     RuleAssociationFinder ruleAssociationFinder = new RuleAssociationFinder(CONTEXT)     ruleAssociationFinder.setRuleId(rule.id.value)     ruleAssociationFinder.setRuleType(RuleType.valueOf(ruleType))     ruleAssociationFinder.setEntityId(entity.id.value)     ruleAssociationFinder.setEntityType(EntityType.valueOf(entityType))     def ruleAssociations = ruleAssociationFinder.findAll()     if (!ruleAssociations || ruleAssociations?.size() == 0){         def ruleAssociation = new RuleAssociation(CONTEXT)         ruleAssociation.entityId = entity.id.value         ruleAssociation.entityType = EntityType.valueOf(entityType)         ruleAssociation.ruleType = RuleType.valueOf(ruleType)         ruleAssociation.setRuleId(rule.id.value)         ruleAssociation.store()         ruleAssociations = [ruleAssociation]     }     return ruleAssociations } def findOrCreateModel(Context CONTEXT, String modelName) {     ModelFinder modelFinder = new ModelFinder(CONTEXT)     modelFinder.setName(modelName)     def model = modelFinder.find()     if (!model){         model = new Model(CONTEXT, modelName);         model.store();     }     return model } https://gist.github.com/axeda/6529288/raw/5ffca58c3c48256b81287d6a6f2d2db63cd5cd2b/AddGeofence.groovy
View full tip
To simplify the development of IIoT applications and solutions on the ThingWorx platform, we introduce the concept of Building Blocks. The intent of Building Blocks is to ease the creation of your own solutions and customization of PTC’s solutions. These Building Blocks are domain specific business logic pre-made for reusability, which means you won’t need to build from scratch on ThingWorx and can accelerate your time to value. What do we mean by Building Blocks? Building Blocks are premade components that enable modular software development. They are reusable, replaceable packages of functionality that can be connected into an architecture framework. Building Blocks allow for quicker development and customization of solutions and applications. What are the different types of Building Blocks?   Connectors  Leverage the same connectors we use for PTC solutions for better overall application performance and seamless transfer of data from disparate devices and systems. Identify the devices and systems you would like to monitor and let the connector do the rest.   Domain Models  Incorporate behavior and data from your devices and systems into a conceptual model of the domain, which is prepackaged based on common use cases. You can also leverage our out of the box models to connect and build dependencies between domains.   Business Logic  Encode real-world business rules that determine how data can be created, stored, and changed. Create KPIs for your devices and systems with these rules and create alerts based on your unique parameters.   UI  Construct widgets to view or analyze key data points in a graphical user interface that you can customize and leverage to extend functionality. Created with manufacturing and service use cases in mind, UI are predesigned to make it easy to view and understand data.   Building Blocks build upon the ThingWorx platform and are the base of all of PTC’s current and future solutions. We will continue to discuss Building Blocks in future posts, but in the meantime: How will you leverage building blocks in your own solutions? Is there more you want to know?   Stay connected, Rachel  
View full tip
Is your team operating an effective DevOps pipeline? DevOps is an important part of a mature, enterprise ready application, but the process isn’t simple.   This expert session will focus on how a full DevOps pipeline looks like and how PTC can help to build a seamless pipeline. Join us for our upcoming Expert Session to learn how to create a Docker image, integrate Azure with Docker and Git, and set up a seamless DevOps pipeline.   When? Thursday, September 30th 2021 | 11 AM EST Host: Tori FIrewind, Senior Engineer in PTC IOT Enterprise Deployment Center Registration link: https://www.ptc.com/en/resources/iiot/webcast/devops-pipeline-thingworx 
View full tip
Integrating LDAP authentication into Thingworx is fairly simple. Since release 5.0 and later, the out-of-the-box (OOTB) Thingworx authenticators already include the necessary code to validate a user's credentials against an LDAP server. These authenticators look to see if an LDAP server is connected every time a user attempts a login, and then further check to see if this user exists in the LDAP server. If the username does exist in LDAP, then Thingworx will check if the password entered is a match to the password stored within LDAP. If the password entered does not match the password stored in LDAP, then Thingworx will next check if the password matches the one stored in Thingworx for that user. So in order for a user to login to Thingworx, they must have a user Thing created for them within Thingworx Composer (this can be done programmatically, see below), and a valid password which matches either an LDAP account password or the password as it is set for that user on the Thing in Thingworx Composer. The first thing a developer needs to do to integrate LDAP is configure their Thingworx instance so that it can find the LDAP server and access its contents. This is done by importing an XML file which will allow the developer to see a Thing that comes with the Thingworx platform (see attached file "directoryServices.xml"). The Thing that needs configuring is called ApacheDS3 and it is a DirectoryServices Thing. The largest task for a developer to do to integrate LDAP into Thingworx involves importing their LDAP users into Thingworx. Getting the LDAP usernames out of the LDAP server will vary depending on which distribution of LDAP is in use. However, once the developer acquires this information, using it to create users in Thingworx is simple. The developer will need to create a Thing Service which creates a dummy password and assigns the LDAP username in the parameters. Then they can pass the parameters into the CreateUser service of the “EntitiyServices” resource: var params = { password: "SOMETHING_COMPLICATED", //dummy password does not matter, but you don't want an accidental match, so make it something very complicated, and standard to your company's LDAP users name: ldap_username, //retrieve from LDAP description: "This user was created as part of LDAP import", //can be whatever you'd like tags: undefined }; Resources["EntityServices"].CreateUser(params); // no return Any users created in this way will be redirected to Squeal if there is no home mashup assigned, so you will have to add an additional bit of code which assigns the home mashups to users, looping through something like this: var params = {     name: "dashboard" //replace this with String name of dashboard (must exist) }; Users[username].SetHomeMashup(params); For full steps on integrating LDAP and Thingworx, including instructions on how to set up an ApacheDS test LDAP server, see the Thingworx support article titled “Integrate LDAP Authentication and Import LDAP User Directory into Thingworx” (reference document – CS221840).
View full tip
The New and Improved DGIS Guide to ThingWorx Development Written by Victoria Firewind of the IoT EDC   The classic Developing Great IoT Solutions guide has been reskinned and revamped for newer versions of ThingWorx! The same information on how to build a quality IoT application is now available for versions of ThingWorx 9.1+, and now, a complete sample application is included to demonstrate these ideas.    Find within the attached archive a PDF with high-level overview information on development and application design geared towards managers and business users, so that everyone can understand the necessary requirements, common terms, and key tips on how to ensure an application is scalable and maintainable right from the very start. Reduce your chances of running into issues between PoC and Go Live by reviewing this information today!   Also find within this PDF a series of tutorials which teach not just how to use the ThingWorx software, but which also educate on how to make good application design choices. A basic rules engine for sending real-time notifications is included here, as well as a complete demo application which illustrates each concept in a real-world use case. This Coffee Machine Demo App relies upon the tutorial entities, which can also now be imported directly using the other XML files provided here. This ensures that anyone can review these concepts, regardless of how much time one can commit or how much knowledge one already has on the subject.   This is a complex guide, and any issues, questions, or bugs found within can be reported right here on this thread. Happy developing from the IoT EDC!
View full tip
This project is developed out of curiosity of how ThingWorx communicates with sensors and vice versa. Immediately a Smart Parking system idea struck to our mind and I started working on it. While heading from home to office I always worry about car parking space in office especially in rainy season. This project will help user in getting parking space. This project has 4 sections as follows, 1) Smart Parking system: A system application developed in ThingWorx guides user to find empty car parking space. Sensors placed at each car parking slot senses the presence of car. A program running on Raspberry Pi board collects sensor information and sends that information to the Smart Car Parking System application in ThingWorx. The data received through sensor is displayed on ThingWorx dashboard/mashup. 2) Live Traffic: This inherits a Google Map and shows the traffic around user's current location. 3) Traffic Blog: If user is visiting a place and have questions regarding parking, traffic condition etc., then user can post their questions here and people around that area can answer it. Questions are not restricted for parking related questions but like best places to visit in areas, restaurant, shops etc. 4) Automobile Wiki: This page provides an documented help regarding anything related to automobile e.g. how to change car tyres?, how to change car wipers? etc.
View full tip
We will host a live Expert Session: "Thingworx Mashup 101 - Do's and Don'ts" on February 24th, 13h30 EST.   Please find below the description of the expert session and the registration link.   Expert Session: Thingworx Mashup 101 - Do's and Don'ts Date and Time: February 24th, 13h30 EST Duration: 1 hour Host: Aanjan Ravi - Technical Product Manager Registration Here: https://www.ptc.com/en/events/thingworx-mashup-101   Description: This session covers the most common and useful tips about how to correctly use Mashup builder, Widgets and Layouts – and what to avoid -  to create applications with good principles of UI/UX and easier to maintain.   Existing Recorded sessions can be found on support portal using the keyword ‘Expert Sessions’. You can also suggest topics for upcoming sessions using this small form.   Here are some recorded sessions that might be of your interest. You can find recordings for the full library of webinars using the keyword ‘Expert Sessions’ in PTC support portal search Thingworx Active Active Clustering This session will cover the main aspects of the High Availability Clustering feature launched with the ThingWorx 9.0 release.   Recoding Link Upgrade to Thingworx 9 – How to Plan / Evaluate Impacts This session highlights the key points you should evaluate to properly plan your upgrade to Thingworx 9. Recording Link Top 5 items to check for Thingworx Performance Troubleshooting How to troubleshoot performance issues in a Thingworx Environment? Here we cover the top 5 investigation steps that will help you understand the source of your environment issues and allow better communication with PTC Technical Support     Recording Link
View full tip
Troubleshooting platform issues is  generally done by using a layer approach, similar to a simplified OSI Model. From bottom to top, the following layers represent the areas to analyze during each step: 1. Physical (Server, power, wired connections): check the server status and condition, CPU and memory levels. 2. Software (Operating system, tomcat, java versions, compatibility, and configuration): refer to the compatibility matrix to ensure the requirements are met; verify Tomcat  java configuration. * Note: Tomcat manager, server status, conveniently provides this information in one place. 3. Network: ensure  proper connectivity, port availability, firewall  configuration, and additional security, if applicable. 4. Application. The main focus of this blog post will concentrate on the step 4. As the Thingworx application is driven by Tomcat, first available tools coming "out-of-the-box" is the built-in Tomcat manager app.  Clicking on the "Server Status" provides the information on the versions, memory usage, processes, times and thread counts. Keep in mind, the default Tomcat maximum thread number is 200. Some additional tools that could assist in troubleshooting java applications and gathering performance metrics are: Javamelody, new relic, profiler4j. These have to be obtained, installed, and configured separately. Javamelody: Free and lightweight monitoring tool which does not do any profiling, safe to use in production environments. It comes with a series of plug-ins including for Grails, Jenkins and Jira. New relic: Real-time Java application monitoring, features code deployment reports, transaction tracing across different tiers and the ability to create alerts. Subscription fee applies. Profiler4j: Profiler4J is a free open-source tool for profiling in Java. It is enabled by passing an argument at start-up with a path to the Profiler4J .jar file. It comes with several graphs and charts showing a call graph with method details, a call tree, a memory monitor, a class list and thread monitoring. From the application perspective, Thingworx composer provides a PlatformSubsystem and LoggingSubsystem: PlatformSubsystem contains such services as GetPerformanceMetrics, GetSummaryInformation, GetThingworxVersion, and more to provide fundamental information for any troubleshooting scenario. LoggingSubsystem contains the logs, log settings, and other monitoring values. List of recommended tools for troubleshooting all layers: Wireshark: monitors network traffic Jstack: monitors memory consumption of specific threads Dynatrace: system performance and web application performance jconsole: system or application performance ​​
View full tip

Only logged in customers with a PTC active maintenance contract can view this content. Learn More

Announcements